Uploading Files With Cake PHP and FileUpload Plugin
Tags: php, cakephp, webdev, tutorial
Published: 2021-10-24 09:19:58

I just release new version of my [FileUpload](https://github.com/MayMeow/cakephp-fileupload) plugin for [CakePHP](https://cakephp.org/).

It will make uploading and downloading files really easi. Want to see? Continue reading.

## Prerequisites

If you have existing application you can skip right to **Create fileupload app**.

- CakePHP 4
- Https server
- PHP 7.x or 8
- php extensions mgstring, intl, SimpleXML, PDO

For more informations check [CakePHP Installation](https://book.cakephp.org/4/en/installation.html)

**OR**

- Docker
- Docker-compose

Right, only those two. I will show you ho to do it in this tutorial.

## Prepare Environment

I will use for this tutorial my [CakePHP starter kit](https://github.com/MayMeow/cakephp-starter-kit), but you are ok to use your existing installation. This starter kit contains all you will need to create your app in no time. It contains:

- CakePHP 4
- PHP 7.4.16, witth all required extensions
- Postgres
- Minio (s3 storage)
- Redis
- Adminer

Ok so start it up, and run comands in shell as follows

```bash
mkdir fileupload-tutorial
cd fileupload-tutorial
docker run --rm --volume $(pwd):/app ghcr.io/maymeow/php-ci-cd/php-ci-cd:7.4.16-cs-1 sh -c "composer create-project --prefer-dist maymeow/cakephp-starter-kit:dev-main /app"
sudo chown -R $USER:$GID .
```

You can run `ls -la` to check if all folders are owned by you. Ok now we create our application

## Create fileupload app

### Installing plugin

First of all we need to install fileupload plugin and load it, run following command

```bash
docker-compose -f docker-compose.dev.yml run --rm cake-app composer require maymeow/file-upload
```

and load it

```bash
docker-compose -f docker-compose.dev.yml run --rm cake-app php bin/cake.php plugin load FileUpload
```

### Creating MVC for Files

Next step is to create MVC for our files controller. For our need is enough to have table with file name and time for creating and modifiing. Create migration as follow

```bash
docker-compose -f docker-compose.dev.yml run --rm cake-app php bin/cake.php bake migration CreateFiles name:string created modified
```

push changes to database

```bash
docker-compose -f docker-compose.dev.yml run --rm cake-app php bin/cake.php migrations migrate
```

and create MVC for our files


```bash
docker-compose -f docker-compose.dev.yml run --rm cake-app php bin/cake.php bake all Files
```

Now is time to start our application with following command


```bash
docker-compose -f docker-compose.dev.yml up -d
```

Navigate to the `http://127.0.0.1:8765/` on your favorite browser an you will see **Welcome to CakePHP 4.2.10 Strawberry** message. This is OK and or applications is now running. To open or files contriller go to `http://127.0.0.1:8765/files`.

### Creating upload view and controller

Standardly CakePHP create controller for viewing, list, editing and adding. But it is not what we need. We want upload file to the server and this one is missing. So create it. First of all create new Modelless form 

```bash
docker-compose -f docker-compose.dev.yml run --rm cake-app php bin/cake.php bake form Upload 
```
Navigate to `src/Form/UploadForm.php` and edit `_buildSchema` as follows

```php
protected function _buildSchema(Schema $schema): Schema
{
    return $schema->addField('file', 'string');
}
```

## Creating view and controller for Upload form

Go to `src/Controller/FilesController` and add following function


```php
public function upload()
{
    $uploadForm = new UploadForm();

    $this->set(compact('uploadForm'));
}
```

and create `upload.php` view in `templates/Files` with following content

```php
/**
 * @var \App\View\AppView $this
 * @var \App\Form\UploadForm $uploadForm
 */
?>
<div class="row">
    <aside class="column">
        <div class="side-nav">
            <h4 class="heading"><?= __('Actions') ?></h4>
            <?= $this->Html->link(__('List Files'), ['action' => 'index'], ['class' => 'side-nav-item']) ?>
        </div>
    </aside>
    <div class="column-responsive column-80">
        <div class="files form content">
            <?= $this->Form->create($uploadForm, ['type' => 'file']) ?>
            <fieldset>
                <legend><?= __('Upload file') ?></legend>
                <?php
                echo $this->Form->control('file',  ['type' => 'file']);
                ?>
            </fieldset>
            <?= $this->Form->button(__('Submit')) ?>
            <?= $this->Form->end() ?>
        </div>
    </div>
</div>

```

Now you can navigate to `http://127.0.0.1:8765/files/upload` and if you see no error but `Upload file` form you are good to go.

### Configuring upload plugin and uploading files

Next stem is load our components to the controller

```bash
public function initialize(): void
{
    parent::initialize(); // TODO: Change the autogenerated stub

    $this->loadComponent('FileUpload.Upload', [
        'fieldName' => 'file',
    ]);

    $this->loadComponent('FileUpload.Download');
}
```

### Uploading files

Now you have loaded Uploadfile plugin's components so there is left some more thing to do. Update upload function as follows

```php
public function upload()
{
    $uploadForm = new UploadForm();

    if ($this->request->is('post')) {
        $uploadedFile = $this->Upload->getFile($this->request);

        $file = $this->Files->newEmptyEntity();
        $file->name = $uploadedFile->getFileName();
        $this->Files->save($file);

        return $this->redirect(['action' => 'index']);
    }

    $this->set(compact('uploadForm'));
}
```

If everything goes right you will see your uploading file name in list of all files.

### Downloading files

Create new function for downloading files in `FilesController`

```php
public function download($fileName)
{
    $downloadedFile = $this->Download->getFile($fileName);

    $response = $this->response;
    $response = $response->withStringBody($downloadedFile->getFileContent());
    $response = $response->withType($downloadedFile->getFileType());

    if ($this->request->getParam('?.download') == true) {
        $response = $response->withDownload($fileName);
    }

    return $response;
}
```

And edit `templates/Files/index.ctp`

```php
# edit line 
<td><?= h($file->name) ?></td>
# to this
<td><?= $this->Html->link($file->name, ['action' => 'download', $file->name, '?' => ['download' => false]]) ?></td>
```

Refresh page. This will display picture you have uploaded befor by clinking on file name. If you want to show download form instead, cahnge `download` to `true`.

### S3 Support

If you want to use S3 instead of local storage, you need make some small changes for it... First of all open your `app_local.php` and add at the end of file following config

```php
'S3' => [
    'version' => 'latest',
    'region'  => 'us-east-1',
    'endpoint' => 'http://cake_minio:9000',
    'use_path_style_endpoint' => true,
    'credentials' => [
        'key'    => 'minioadmin',
        'secret' => 'minioadmin',
    ],
]
```

Now navigate to the `http://127.0.0.1:9001` and login with default creadentials, both password and username are `minioadmin`. In right menu go to buckets and create bucket for your needs. For this purposes i create bucket with name `fileupload.tutorial`.

You can check it in **Object browser** on sidebar.

Now let's configure our application. Update `FilesController` as follows:

```php
public function initialize(): void
{
    parent::initialize(); // TODO: Change the autogenerated stub

    $this->loadComponent('FileUpload.Upload', [
        'fieldName' => 'file',
        'storagePath' => 'fileupload.tutorial', // bucket name
        'storage_type' => 's3'
    ]);

    $this->loadComponent('FileUpload.Download', [
        'storagePath' => 'fileupload.tutorial', // bucket name
        'storage_type' => 's3'
    ]);
}
```

Ok, That all now you have application which can upload / download files to local or S3 storage.  Easy no?

### Conclusion

I'm lazy person so I wanted to make it more easier (dont telling that CakePHP on itself is not easy to start) to start my applications. And with Starter kit you are good to go in few seconds and you can focus on your application.

With Fileupload plugin you can upload/download files on bot local and s3 storage literally with one line of code (dont counting configuration). I hope it will make easier for you to manage files uploading in your application to. If you find bugs please report them on [FileUpload's Issue tracker](https://github.com/MayMeow/cakephp-fileupload/issues), also if you have some ideas i have opened [Discussions](https://github.com/MayMeow/cakephp-fileupload/discussions) for project where you can share your ideas.

Anyway if you like this project you can contibute to it on [GitHub](https://github.com/MayMeow/cakephp-fileupload) repository which is also participating in Hacktoberfest.

Same is for [CakePHP starter kit](https://github.com/MayMeow/cakephp-starter-kit) which is on Github too.


### Bonus

All source codes from this tutorial are awailable here on [FileUpload Tutorial Github](https://github.com/MayMeow/fileupload-tutorial).

Thank you for reading and have a nice day!

Originally posted on [themaymeow.com blog](https://www.themaymeow.com/en/posts/2021/10/uploading-files-with-cake-php-and-fileupload-plugin/)