File uploads
A file upload is a form with a file field plus code that moves the submitted file to a permanent folder. In CakePHP an uploaded file arrives in the action as an object with methods for its name, size and moving.
Preparation
Create the webroot/uploads folder by hand; everything inside webroot is reachable by the site address. The file name is stored in the cover column of the articles table:
bin/cake bake migration AddCoverToArticles cover:string
bin/cake migrations migrateThe form
<div>
<?= $this->Form->create($article, ['type' => 'file']) ?>
<?= $this->Form->control('title') ?>
<?= $this->Form->control('cover_file', ['type' => 'file', 'label' => 'Cover']) ?>
<?= $this->Form->button('Save') ?>
<?= $this->Form->end() ?>
</div>The 'type' => 'file' option in create() is required: without it the browser sends only the file name. The field is called cover_file, not cover, so that the file object does not end up in the entity instead of a string.
Getting the file
// src/Controller/ArticlesController.php
$file = $this->request->getUploadedFile('cover_file');
if ($file !== null && $file->getError() === UPLOAD_ERR_OK) {
$name = bin2hex(random_bytes(16)) . '.jpg';
$file->moveTo(WWW_ROOT . 'uploads' . DS . $name);
$article->cover = $name;
}This code goes into the add() action after patchEntity() and before save(). getUploadedFile() returns the file object, moveTo() moves it to webroot/uploads, and the name is written to the cover column. The WWW_ROOT constant is the path to the webroot folder.
File methods
| Method | What it returns |
|---|---|
getClientFilename() | The file name on the visitor's computer |
getSize() | The size in bytes |
getClientMediaType() | The type reported by the browser |
getError() | An error code, UPLOAD_ERR_OK on success |
moveTo($path) | Moves the file |
Checking
Before saving a file, check its extension and size:
$extension = strtolower(pathinfo($file->getClientFilename(), PATHINFO_EXTENSION));
if (!in_array($extension, ['jpg', 'jpeg', 'png'], true)) {
$this->Flash->error('Upload a JPG or PNG image.');
} elseif ($file->getSize() > 2 * 1024 * 1024) {
$this->Flash->error('The file is larger than 2 MB.');
} else {
$name = bin2hex(random_bytes(16)) . '.' . $extension;
$file->moveTo(WWW_ROOT . 'uploads' . DS . $name);
$article->cover = $name;
}A file with another extension or one that is too large is not saved, and the visitor sees a message.
Showing the image
<div>
<?php if ($article->cover): ?>
<?= $this->Html->image('/uploads/' . $article->cover, ['alt' => $article->title]) ?>
<?php endif; ?>
</div>A path that starts with a slash is counted by the Html helper from the webroot folder. The cover appears on the article page.
Common mistake
Saving a file under the name sent by the visitor is dangerous: the name may contain ../ or the .php extension, and someone else's executable code ends up on the server. Always generate the name yourself as a random string and take only a checked extension from the allowed list.