CakePHP 3 - Image upload handling is currently too complex.
I'm working on a WIN7 System with XAMPPv3.2.2
I have this test view - Clients\file.ctp for testing uploads
<div>
<?= $this->Form->create($client, ['type' => 'file']) ?>
<?= $this->Form->input('logo', ['type' => 'file']); ?>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
</div>
I upload an image named logo (successfully) to the server via this controller - ClientsController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
use Cake\Filesystem\Folder;
class ClientsController extends AppController
{
public function file() {
$client = $this->Clients->newEntity();
if ($this->request->is('post')) {
$this->log($client, 'debug');
$this->log($this->request->data, 'debug');
$client = $this->Clients->patchEntity($client, $this->request->data);
// Here my first error was included - I have setup the model including LOGO as a varchar(250) field.
// This is transferred into a text field but the upload needs to be an array
// How do we have to define an image field within the Database?
if (!empty($this->request->data) && !empty($this->request->data['logo']) && !empty($this->request->data['logo']['name']))
$client->logo = $this->request->data['logo'];
// Assigning test to see if something is saved within the DB
$client->name = 'TEST';
if ($this->Clients->save($client)) {
$this->log($client, 'debug');
$this->log($this->request->data, 'debug');
$this->Flash->success(__('The client has been saved.'));
return $this->redirect(['action' => 'file']);
} else {
//debug($client->errors());
$this->Flash->error(__('The client could not be saved. Please, try again.'));
}
}
$this->set(compact('client'));
$this->set('_serialize', ['client']);
}
Question 1: I have had to assign the uploaded array on myself to the model variable that it works and gets uploaded. How should the database model look like to get an image upload generated via BAKE? I could not find this documented.
I have setup following ClientsTable.php to get this data validated
<?php
namespace App\Model\Table;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class ClientsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('clients');
$this->displayField('name');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
// I handle the upload of the image within this Behavior.
// This is working well! This validator is triggered with beforeSave event
$this->addBehavior('Upload', [
'field' => 'logo',
'uploadPath' => 'logos',
'setUniqueGuidName' => true
]);
}
public function validationDefault(Validator $validator)
{
$validator
->uuid('id')
->allowEmpty('id', 'create');
$validator
->allowEmpty('title');
$validator
->add('logo', [
'uploadError' => [
'rule' => 'uploadError',
'message' => 'The cover image upload failed.',
'allowEmpty' => TRUE,
],
'mimeType' => [
'rule' => array('mimeType', array('image/gif', 'image/png', 'image/jpg', 'image/jpeg')),
'message' => 'Please only upload images (gif, png, jpg).',
'allowEmpty' => TRUE,
],
'fileSize' => [
'rule' => array('fileSize', '<=', '1MB'),
'message' => 'Cover image must be less than 1MB.',
'allowEmpty' => TRUE,
],
])
->allowEmpty('logo');
}
}
Question 2: Why is the validation not triggered? I can upload PDF files and also files which are bigger than 1MB and get always a successful message. I can also find the documents uploaded in the directory! With files which are bigger than 1MB i get an interesting exception
2016-02-25 03:17:07 Error: [RuntimeException] Cannot validate mimetype for a missing file
Request URL: /pam/clients/file
Referer URL: http://localhost/pam/clients/file
Stack Trace:
#0 [internal function]: Cake\Validation\Validation::mimeType(Array, Array)
#1 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Validation\RulesProvider.php(71): ReflectionMethod->invokeArgs(NULL, Array)
#2 [internal function]: Cake\Validation\RulesProvider->__call('mimeType', Array)
#3 [internal function]: Cake\Validation\RulesProvider->mimeType(Array, Array, Array)
#4 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Validation\ValidationRule.php(138): call_user_func_array(Array, Array)
#5 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Validation\Validator.php(1410): Cake\Validation\ValidationRule->process(Array, Array, Array)
#6 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Validation\Validator.php(137): Cake\Validation\Validator->_processRules('logo', Object(Cake\Validation\ValidationSet), Array, true)
#7 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\ORM\Marshaller.php(193): Cake\Validation\Validator->errors(Array, true)
#8 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\ORM\Marshaller.php(466): Cake\ORM\Marshaller->_validate(Array, Array, true)
#9 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\ORM\Table.php(2073): Cake\ORM\Marshaller->merge(Object(App\Model\Entity\Client), Array, Array)
#10 C:\Users\D052192\OneDrive\xampp\htdocs\pam\src\Controller\ClientsController.php(102): Cake\ORM\Table->patchEntity(Object(App\Model\Entity\Client), Array)
#11 [internal function]: App\Controller\ClientsController->file()
#12 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\friendsofcake\crud\src\Controller\ControllerTrait.php(51): call_user_func_array(Array, Array)
#13 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Routing\Dispatcher.php(114): App\Controller\AppController->invokeAction()
#14 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Routing\Dispatcher.php(87): Cake\Routing\Dispatcher->_invoke(Object(App\Controller\ClientsController))
#15 C:\Users\D052192\OneDrive\xampp\htdocs\pam\webroot\index.php(37): Cake\Routing\Dispatcher->dispatch(Object(Cake\Network\Request), Object(Cake\Network\Response))
#16 {main}
Here is the output of the two log entries from the controller
2016-02-25 03:36:36 Debug: {
"logo": "C:\\Users\\D052192\\OneDrive\\xampp\\htdocs\\pam\\webroot\\img\\logos\\56ce76c3edb88.pdf",
"name": "TEST",
"created": "2016-02-25T03:36:35+0000",
"modified": "2016-02-25T03:36:35+0000",
"id": "6d84cf0c-a1b9-4c1b-8e61-98dce6b9a659"
}
2016-02-25 03:36:36 Debug: Array
(
[logo] => Array
(
[name] => TK.pdf
[type] => application/pdf
[tmp_name] => C:\Users\D052192\OneDrive\xampp\tmp\php57F0.tmp
[error] => 0
[size] => 6437
)
)
I have searched a lot via Google and also within the documents but could not find out if I have an issue with the setup of the validation call it self or with the assignment of the logo variable.
I have logged the flow within CakeCore - Validation.php.
First I call uploadErrors which is executed without error response if I upload a PDF file. Second is the mimeType Check which gets following values
public static function mimeType($check, $mimeTypes = [])
$check = Array
(
[name] => TK.pdf
[type] => application/pdf
[tmp_name] => C:\Users\D052192\OneDrive\xampp\tmp\phpB7C.tmp
[error] => 0
[size] => 6437
)
$mimeTypes = Array
(
[0] => image/gif
[1] => image/png
[2] => image/jpg
[3] => image/jpeg
)
Together with these values this function returns FALSE. So the question is, why is this false not processed? Where should I look next?
@ndm Thanks a lot for all your hints! You're right! With this call
$client = $this->Clients->patchEntity($client, $this->request->data);
the data is validated and only set if, the validation was positive.
Setting the $logo
to the value within the request, cleared the validation status and therefor was not shown on the screen. I have commented this out
if (!empty($this->request->data)
&& !empty($this->request->data['logo'])
&& !empty($this->request->data['logo']['name']))
$client->logo = $this->request->data['logo'];
But in that case, if i transfer a valid image, the $client->logo = ""
is empty. If i transfer an invalid image, the $client->logo
is not set at all.
So i solved my issue with extending the if-statement as follow
if (!is_null($client->logo)
&& !empty($this->request->data)
&& !empty($this->request->data['logo'])
&& !empty($this->request->data['logo']['name']))
$client->logo = $this->request->data['logo'];
I set the $logo
only if the validation was accepted and set before. The final question is only, why $client->logo = ""
is empty if a valid image was transferred and has to be assigned manually.