CakePHP:文件上传时 class Laminas\Diactoros\UploadedFile 的对象无法转换为字符串
CakePHP : Object of class Laminas\Diactoros\UploadedFile could not be converted to string while file upload
我正在尝试在 cakephp 版本 4 中上传文件。
我正在关注这个 docs
我在控制器里试过了
if ($this->request->is('post')) {
$image = $this->request->getData('image');
$fileName = $image->getClientFilename();
$targetPath = WWW_ROOT.'img'.DS.$fileName;
$image->moveTo($targetPath);
$user = $this->Users->patchEntity($user, $this->request->getData()); //line 58
$user->image = $fileName;
$this->Users->save($user);
}
图片上传正常,名称也保存在数据库中。但是当发生验证错误时,我得到
Warning (4096): Object of class Laminas\Diactoros\UploadedFile could not be converted to string [CORE\src\Database\Type\StringType.php, line 97]
日志
Cake\Database\Type\StringType::marshal() - CORE\src\Database\Type\StringType.php, line 97
Cake\ORM\Marshaller::Cake\ORM\{closure}() - CORE\src\ORM\Marshaller.php, line 78
Cake\ORM\Marshaller::merge() - CORE\src\ORM\Marshaller.php, line 558
Cake\ORM\Table::patchEntity() - CORE\src\ORM\Table.php, line 2761
App\Controller\UsersController::add() - APP/Controller\UsersController.php, line 58
Cake\Controller\Controller::invokeAction() - CORE\src\Controller\Controller.php, line 524
Cake\Controller\ControllerFactory::invoke() - CORE\src\Controller\ControllerFactory.php, line 79
Cake\Http\BaseApplication::handle() - CORE\src\Http\BaseApplication.php, line 229
Cake\Http\Runner::handle() - CORE\src\Http\Runner.php, line 77
Cake\Http\Runner::handle() - CORE\src\Http\Runner.php, line 77
Cake\Http\Middleware\CsrfProtectionMiddleware::process() - CORE\src\Http\Middleware\CsrfProtectionMiddleware.php, line 132
Cake\Http\Runner::handle() - CORE\src\Http\Runner.php, line 73
Cake\Http\Runner::run() - CORE\src\Http\Runner.php, line 58
Cake\Routing\Middleware\RoutingMiddleware::process() - CORE\src\Routing\Middleware\RoutingMiddleware.php, line 162
Cake\Http\Runner::handle() - CORE\src\Http\Runner.php, line 73
Cake\Routing\Middleware\AssetMiddleware::process() - CORE\src\Routing\Middleware\AssetMiddleware.php, line 68
首先,您的代码示例盲目地信任客户端,它接受任何文件,并允许通过将路径作为文件名传递来选择任意目标位置!切勿在没有 validating/sanitizing 的情况下使用客户端数据,相信客户端提供的文件日期会导致各种漏洞!!!
话虽这么说,通常您要么使用单独的字段来上传文件,要么使用在数据编组时不转换数据的自定义数据库类型,以便您可以在之后手动转换它。目前,您似乎使用了应该在数据库中保存字符串的上传字段,因此编组器将尝试将输入转换为字符串,这当然会失败。
因此,例如,将表单控件从 image
重命名为 image_file
(数据库中不应存在此类列),为 image_file
添加适当的验证规则,这几行,以确保上传有效,即检查类型、大小、名称等:
$validator
->notEmptyFile('image_file')
->uploadedFile('image_file', [
'types' => ['image/png'], // only PNG image files
'minSize' => 1024, // Min 1 KB
'maxSize' => 1024 * 1024 // Max 1 MB
])
->add('image_file', 'minSize', [
'rule' => ['imageSize', [
// Min 10x10 pixel
'width' => [Validation::COMPARE_GREATER_OR_EQUAL, 10],
'height' => [Validation::COMPARE_GREATER_OR_EQUAL, 10],
]]
])
->add('image_file', 'maxSize', [
'rule' => ['imageSize', [
// Max 100x100 pixel
'width' => [Validation::COMPARE_LESS_OR_EQUAL, 100],
'height' => [Validation::COMPARE_LESS_OR_EQUAL, 100],
]]
])
->add('image_file', 'filename', [
'rule' => function (UploadedFileInterface $file) {
// filename must not be a path
$filename = $file->getClientFilename();
if (strcmp(basename($filename), $filename) === 0) {
return true;
}
return false;
}
])
->add('image_file', 'extension', [
'rule' => ['extension', ['png']] // .png file extension only
]);
然后在补丁之后处理上传,即在验证发生之后,只有在验证成功时才移动文件!
$user = $this->Users->patchEntity($user, $this->request->getData());
if (!$user->getErrors()) {
// never trust anything in `$image` if you haven't properly validated it!!!
$image = $this->request->getData('image_file');
$fileName = $image->getClientFilename();
// if you want to take it a step further, drop the user supplied filename
// $fileName = \Cake\Utility\Security::hash($image->getClientFilename(), 'sha1');
// and re-encode the image to remove possible dangerous payloads, meta data, etc
// reEncodeImage($image->getStream());
$image->moveTo(WWW_ROOT . 'img' . DS . $fileName);
$user->image = $fileName;
}
if ($this->Users->save($user)) {
// success
} else {
// failure
}
一旦掌握了工作原理,您可能应该考虑在 table class 或行为(beforeSave()
event/callback 在执行上传逻辑时非常流行)或某种服务。
您可能还想看看现有插件如何处理上传,例如 https://github.com/FriendsOfCake/awesome-cakephp#user-content-files
// add
public function add()
{
$info = $this->Infos->newEmptyEntity(); //debug($info);
$this->Authorization->authorize($info);
if ($this->request->is('post')) {
$data = $this->request->getData(); //debug($data);
$dimage = $this->request->getData('image');
$name = $dimage->getClientFilename(); //debug($name);
$type = $dimage->getClientMediaType(); //debug($type);
$size = $dimage->getSize(); //debug($size);
$tmpName = $dimage->getStream()->getMetadata('uri'); //debug($tmpName);
$error = $dimage->getError(); //debug($error);
$targetPath = WWW_ROOT . 'img' . DS . 'infos' . DS . $name; //debug($targetPath);
if ($type == 'image/jpeg' || $type == 'image/jpg' || $type == 'image/png') {
if (!empty($name)) {
if ( ($size > 0) && ($size < 1048576) && ($error == 0)) { // less 1MB
$dimage->moveTo($targetPath);
}
}
}
$data['image'] = $name; //debug($info); //debug($data);
$info = $this->Infos->patchEntity($info, $data);
$info->user_id = $this->request->getAttribute('identity')->getIdentifier();
if ($this->Infos->save($info)) {
$this->Flash->success(__('The info has been saved.'));
return $this->redirect(['controller' => 'Infos', 'action' => 'view', $info->id]);
} else {
$this->Flash->error(__('The info could not be saved. Please, try again.'));
return $this->redirect(['controller'=>'Infos', 'action' => 'add']);
}
}
$infoCategories = $this->Infos->InfoCategories->find('list', ['limit' => 200])->all();
$this->set(compact('info','infoCategories'));
}
我正在尝试在 cakephp 版本 4 中上传文件。
我正在关注这个 docs
我在控制器里试过了
if ($this->request->is('post')) {
$image = $this->request->getData('image');
$fileName = $image->getClientFilename();
$targetPath = WWW_ROOT.'img'.DS.$fileName;
$image->moveTo($targetPath);
$user = $this->Users->patchEntity($user, $this->request->getData()); //line 58
$user->image = $fileName;
$this->Users->save($user);
}
图片上传正常,名称也保存在数据库中。但是当发生验证错误时,我得到
Warning (4096): Object of class Laminas\Diactoros\UploadedFile could not be converted to string [CORE\src\Database\Type\StringType.php, line 97]
日志
Cake\Database\Type\StringType::marshal() - CORE\src\Database\Type\StringType.php, line 97
Cake\ORM\Marshaller::Cake\ORM\{closure}() - CORE\src\ORM\Marshaller.php, line 78
Cake\ORM\Marshaller::merge() - CORE\src\ORM\Marshaller.php, line 558
Cake\ORM\Table::patchEntity() - CORE\src\ORM\Table.php, line 2761
App\Controller\UsersController::add() - APP/Controller\UsersController.php, line 58
Cake\Controller\Controller::invokeAction() - CORE\src\Controller\Controller.php, line 524
Cake\Controller\ControllerFactory::invoke() - CORE\src\Controller\ControllerFactory.php, line 79
Cake\Http\BaseApplication::handle() - CORE\src\Http\BaseApplication.php, line 229
Cake\Http\Runner::handle() - CORE\src\Http\Runner.php, line 77
Cake\Http\Runner::handle() - CORE\src\Http\Runner.php, line 77
Cake\Http\Middleware\CsrfProtectionMiddleware::process() - CORE\src\Http\Middleware\CsrfProtectionMiddleware.php, line 132
Cake\Http\Runner::handle() - CORE\src\Http\Runner.php, line 73
Cake\Http\Runner::run() - CORE\src\Http\Runner.php, line 58
Cake\Routing\Middleware\RoutingMiddleware::process() - CORE\src\Routing\Middleware\RoutingMiddleware.php, line 162
Cake\Http\Runner::handle() - CORE\src\Http\Runner.php, line 73
Cake\Routing\Middleware\AssetMiddleware::process() - CORE\src\Routing\Middleware\AssetMiddleware.php, line 68
首先,您的代码示例盲目地信任客户端,它接受任何文件,并允许通过将路径作为文件名传递来选择任意目标位置!切勿在没有 validating/sanitizing 的情况下使用客户端数据,相信客户端提供的文件日期会导致各种漏洞!!!
话虽这么说,通常您要么使用单独的字段来上传文件,要么使用在数据编组时不转换数据的自定义数据库类型,以便您可以在之后手动转换它。目前,您似乎使用了应该在数据库中保存字符串的上传字段,因此编组器将尝试将输入转换为字符串,这当然会失败。
因此,例如,将表单控件从 image
重命名为 image_file
(数据库中不应存在此类列),为 image_file
添加适当的验证规则,这几行,以确保上传有效,即检查类型、大小、名称等:
$validator
->notEmptyFile('image_file')
->uploadedFile('image_file', [
'types' => ['image/png'], // only PNG image files
'minSize' => 1024, // Min 1 KB
'maxSize' => 1024 * 1024 // Max 1 MB
])
->add('image_file', 'minSize', [
'rule' => ['imageSize', [
// Min 10x10 pixel
'width' => [Validation::COMPARE_GREATER_OR_EQUAL, 10],
'height' => [Validation::COMPARE_GREATER_OR_EQUAL, 10],
]]
])
->add('image_file', 'maxSize', [
'rule' => ['imageSize', [
// Max 100x100 pixel
'width' => [Validation::COMPARE_LESS_OR_EQUAL, 100],
'height' => [Validation::COMPARE_LESS_OR_EQUAL, 100],
]]
])
->add('image_file', 'filename', [
'rule' => function (UploadedFileInterface $file) {
// filename must not be a path
$filename = $file->getClientFilename();
if (strcmp(basename($filename), $filename) === 0) {
return true;
}
return false;
}
])
->add('image_file', 'extension', [
'rule' => ['extension', ['png']] // .png file extension only
]);
然后在补丁之后处理上传,即在验证发生之后,只有在验证成功时才移动文件!
$user = $this->Users->patchEntity($user, $this->request->getData());
if (!$user->getErrors()) {
// never trust anything in `$image` if you haven't properly validated it!!!
$image = $this->request->getData('image_file');
$fileName = $image->getClientFilename();
// if you want to take it a step further, drop the user supplied filename
// $fileName = \Cake\Utility\Security::hash($image->getClientFilename(), 'sha1');
// and re-encode the image to remove possible dangerous payloads, meta data, etc
// reEncodeImage($image->getStream());
$image->moveTo(WWW_ROOT . 'img' . DS . $fileName);
$user->image = $fileName;
}
if ($this->Users->save($user)) {
// success
} else {
// failure
}
一旦掌握了工作原理,您可能应该考虑在 table class 或行为(beforeSave()
event/callback 在执行上传逻辑时非常流行)或某种服务。
您可能还想看看现有插件如何处理上传,例如 https://github.com/FriendsOfCake/awesome-cakephp#user-content-files
// add
public function add()
{
$info = $this->Infos->newEmptyEntity(); //debug($info);
$this->Authorization->authorize($info);
if ($this->request->is('post')) {
$data = $this->request->getData(); //debug($data);
$dimage = $this->request->getData('image');
$name = $dimage->getClientFilename(); //debug($name);
$type = $dimage->getClientMediaType(); //debug($type);
$size = $dimage->getSize(); //debug($size);
$tmpName = $dimage->getStream()->getMetadata('uri'); //debug($tmpName);
$error = $dimage->getError(); //debug($error);
$targetPath = WWW_ROOT . 'img' . DS . 'infos' . DS . $name; //debug($targetPath);
if ($type == 'image/jpeg' || $type == 'image/jpg' || $type == 'image/png') {
if (!empty($name)) {
if ( ($size > 0) && ($size < 1048576) && ($error == 0)) { // less 1MB
$dimage->moveTo($targetPath);
}
}
}
$data['image'] = $name; //debug($info); //debug($data);
$info = $this->Infos->patchEntity($info, $data);
$info->user_id = $this->request->getAttribute('identity')->getIdentifier();
if ($this->Infos->save($info)) {
$this->Flash->success(__('The info has been saved.'));
return $this->redirect(['controller' => 'Infos', 'action' => 'view', $info->id]);
} else {
$this->Flash->error(__('The info could not be saved. Please, try again.'));
return $this->redirect(['controller'=>'Infos', 'action' => 'add']);
}
}
$infoCategories = $this->Infos->InfoCategories->find('list', ['limit' => 200])->all();
$this->set(compact('info','infoCategories'));
}