Symfony 4.4:为图像文件上传保留的实体/模型属性由表单系统设置更新(结果始终为空 - 没有错误)

Posted

技术标签:

【中文标题】Symfony 4.4:为图像文件上传保留的实体/模型属性由表单系统设置更新(结果始终为空 - 没有错误)【英文标题】:Symfony 4.4: entity/model property reserved for the image file upload is newer being set by the form system (result is always null - no errors) 【发布时间】:2020-09-08 06:08:11 【问题描述】:

我正在尝试使用内部表单系统为基于 Symfony 4.4 的 API 服务实现输入数据过滤器。

在大多数情况下,它工作得很好——整数或基于文本的字段。当涉及到文件/图像字段时,它以某种方式无法按预期工作。我尝试了官方文档中的各种集成选项,但没有成功。

由于遗留代码以及提供的上传字段名称与确切实体之间的不一致,我准备了一个模型,而不是使用之后实际存储上传文件数据的实体模型:

<?php

namespace App\Model;

use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Asserts;

class Avatar 
    /**
     * @var File
     *
     * @Asserts\Image()
     * #Asserts\NotBlank() // Temporary disabled because this property never gets set due to the unknown reason.
     */
    protected $file = null;

    public function setFile(?File $file = null): self
    
        $this->file = $file;

        return $this;
    

    public function getFile(): ?File
    
        return $this->file;
    

表单类型如下所示:

<?php

namespace App\Form;

use App\Model\Avatar;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Validator\Constraints;

class AvatarType extends AbstractType

    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('file', Type\FileType::class, [
                'label' => 'Image',
                'required' => true,
                'mapped' => true,
                'constraints' => [
                    new Constraints\Image([
                        'maxSize' => '5M'
                    ])
                ]
            ])
            ;
    

    public function configureOptions(OptionsResolver $resolver)
    
        $resolver->setDefaults([
            'data_class' => Avatar::class,
            'csrf_protection' => false
        ]);
    

最后是控制器部分:

<?php

namespace App\Controller\Api;

use App\Controller\Api\BaseController;
use App\Entity\User;
use App\Model\Avatar;
use App\Form\AvatarType;
use App\Repository\UserRepository;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

/**
 * @Route("/me/avatar", name="app_api.me.avatar", methods="POST")
 */
class AvatarController extends BaseController

    public function uploadAvatar(User $user, Request $request)
    
        $avatar = new Avatar();
        $form = $this->createForm(AvatarType::class, $avatar);
        $form->submit($request->request->all());
        if ($form->isSubmitted() && (!$form->isValid())) 
            throw new \Exception((string) $form->getErrors(true));
        

        dd($avatar->getFile());

        ...
    

当我尝试使用带有 body -> form-data -> 文件属性集的 PostMan 向此端点发出 POST 请求时,找到一些选择的图像文件,由于 $avatar->getFile() 在控制器。

如果我使用 dd($form->getData()); 结果是相似的而不是 dd($avatar->getFile());

AvatarController.php on line 29:
App\Model\Avatar #213795
  #file: null

我尝试过使用 FormType 字段属性 'mapped' => false 以及以下获取数据的方法,但结果是相同的 - 属性 'file' 永远不会被设置并且没有报告错误。它适用于除 FileType 之外的所有其他字段类型(我已经测试过)。

dd($form['file']->getData()); // results in null

如果我添加其他类型的字段,例如 TextType,它们会按预期工作:

AvatarController.php on line 29:
App\Model\Avatar #213795
  #file: null
  #test: "some input text"

如果我使用来自输入请求的直接数据,它适用于文件属性,但它是不安全的,并且没有 Symfony 功能提供的任何限制。

/** @var UploadedFile $ufile */
$ufile = $request->files->get('file');
dd($ufile);

=>

AvatarController.php on line 34:
Symfony\Component\HttpFoundation\File\UploadedFile #528
  -test: false
  -originalName: "67922301_10219819530703883_7215519506519556096_n.jpg"
  -mimeType: "image/jpeg"
  -error: 0
  path: "/tmp"
  filename: "phpFHPPNL"
  basename: "phpFHPPNL"
  pathname: "/tmp/phpFHPPNL"
  extension: ""
  realPath: "/tmp/phpFHPPNL"
  aTime: 2020-05-21 17:02:49
  mTime: 2020-05-21 17:02:49
  cTime: 2020-05-21 17:02:49
  inode: 1451769
  size: 145608
  perms: 0100600
  owner: 1000
  group: 1000
  type: "file"
  writable: true
  readable: true
  executable: false
  file: true
  dir: false
  link: false

我在这里做错了什么?有什么想法吗?

【问题讨论】:

【参考方案1】:

问题出在$form-&gt;submit($request-&gt;request-&gt;all()); 行。 $request-&gt;request 相当于$_POST,在$_FILES 超全局中可用的PHP 文件可通过$request-&gt;files 获得。无论如何,避免此类问题的最佳方法是致电$form-&gt;handleRequest($request);,而不是手动提交数据。

【讨论】:

感谢您的回答。你为我指明了正确的方向。您对 $request 的看法是正确的,但 handleRequest 方法有点棘手。如果表单类型中设置的方法不匹配,它不会提交表单(有时可以方便地为多个方法使用相同的表单类型,例如 POST、PUT、PATCH 以避免过多的代码)并检查是否包含表单名称在提供的数据中 - 这对 API 端点完全没有用。 我通过合并 POST 和 FILES 参数如 $form->submit(array_merge($request->request->all(), $request->files->all())) 让它工作;但我不喜欢这个解决方案。明天会尝试找出其他的东西。

以上是关于Symfony 4.4:为图像文件上传保留的实体/模型属性由表单系统设置更新(结果始终为空 - 没有错误)的主要内容,如果未能解决你的问题,请参考以下文章

在单个表Symfony中处理filesUpload

Symfony 5. EasyAdmin 3. VichUploader。同时上传多个文件

Symfony2/Doctrine:如何从实体类中持久化一个实体?

Symfony2 中多个文件上传的问题

Symfony 4.4如何使用collectionType从0个字段开始

如何让 Symfony 2 与学说 ORM 只保留一次相关实体