Here is the validation for my input file "image" :
public function validationDefault(Validator $validator): Validator
{
$validator = parent::validationDefault($validator);
$validator
->allowEmptyFile('image')
->add('image', 'uploadError', [
'rule' => function ($value, $context) {
foreach ($value as $v) {
return Validation::uploadError($v, true);
}
},
'last' => true,
'message' => 'Upload error'
])
->add('image', 'mimeType', [
'rule' => function ($value, $context) {
foreach ($value as $v) {
return Validation::mimeType($v, [
'image/png',
'image/gif',
'image/pjpeg',
'image/jpeg'
]);
}
},
'message' => 'Bad mime type.',
]);
}
It works well when a file is submitted, but when no file is uploaded the mimeType validation error is triggered.
So I've modified mimeType rule to check if a file is uploaded before checking mimeType like that :
public function validationDefault(Validator $validator): Validator
{
$validator = parent::validationDefault($validator);
$validator
->allowEmptyFile('image')
->add('image', 'uploadError', [
'rule' => function ($value, $context) {
foreach ($value as $v) {
return Validation::uploadError($v, true);
}
},
'last' => true,
'message' => 'Upload error'
])
->add('image', 'mimeType', [
'rule' => function ($value, $context) {
// Added to avoid mimeType validation when no file is uploaded
if ($value[0]->getError() === UPLOAD_ERR_NO_FILE) {
return true;
}
foreach ($value as $v) {
return Validation::mimeType($v, [
'image/png',
'image/gif',
'image/pjpeg',
'image/jpeg'
]);
}
},
'message' => 'Bad mime type.',
]);
}
It works but it doesn't seem so clean to me to add
if ($value[0]->getError() === UPLOAD_ERR_NO_FILE) {return true;}
on every single rule that could be added after mime type check (for example I will add file size check, image width check, etc.)
Is there a better way to add validation rules on file only if a file is submitted ?
CodePudding user response:
Seeing how your upload functionality is currently structured, that is all uploads are stored in the same table, and a behavior handles them via a hasMany
association, including for models that only accept a single file, and the frontend using multi-file inputs, one possible solution would be to simply remove the field in case of an empty file upload.
You can change data via the Model.beforeMarshal
event/callback, it will run when creating/patching entities, before validation is applied, for example:
public function beforeMarshal(
\Cake\Event\EventInterface $event,
\ArrayAccess $data,
\ArrayObject $options
): void {
if (
isset($data['image'][0]) &&
$data['image'][0] instanceof \Psr\Http\Message\UploadedFileInterface &&
$data['image'][0]->getError() === \UPLOAD_ERR_NO_FILE
) {
unset($data['image']);
}
}
This would remove the image
field in case it is an array, and the first element is an empty uploaded file object. This is what you'd receive for a multi-file input where no file has been selected, as well as for your single-file input that uses an array for the name.
Your validation rules would then simply not run when the field isn't present anymore, and when they do run, they would not have to allow that situation, but instead could strictly require valid uploaded file objects.
So, for example for a model that should only accept a single upload, you could do something like this to ensure the value is an array that holds exactly one element with an uploaded file object:
$validator
->add('image', 'exactlyOneUploadedFile', [
'rule' => function ($value, $context) {
if (
is_array($value) &&
count($value) === 1 &&
$value[0] instanceof \Psr\Http\Message\UploadedFileInterface
) {
return true;
}
return false;
},
// ...
])
// ...
Similarly, for a model that should accept multiple uploads, you could do something like this to ensure the value is an array that holds one or more elements of only uploaded file objects:
$validator
->add('image', 'onlyUploadedFiles', [
'rule' => function ($value, $context) {
if (
!is_array($value) ||
count($value) < 1
) {
return false;
}
foreach ($value as $upload) {
if (!($upload instanceof \Psr\Http\Message\UploadedFileInterface)) {
return false;
}
}
return true;
},
// ...
])
// ...
It is important that you really make these checks strict! The validation rules that you've posted would for example only check the first entry in the array, but do not check whether more element exist, which could possibly result in a situation where more than one upload is being sent, and the additional uploads slip through unvalidated!
See also