【问题标题】:Symfony form: Uploaded file - "This value should be of type string"Symfony 表单:上传的文件 - “此值应为字符串类型”
【发布时间】:2019-11-03 13:37:43
【问题描述】:

[更新]:2019/06/24 - 23;28

用表单上传文件,遇到如下错误:

这个值应该是字符串类型

表单构建器应设置为FileType

FormType

class DocumentType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        /** @var Document $salle */
        $document=$options['data']; //Unused for now
        $dataRoute=$options['data_route']; //Unused for now

        $builder->add('nom')
                ->add('description')
                ->add('fichier', FileType::class, array(
                    //'data_class' is not the problem, tested without it.
                    //see comments if you don't know what it does.
                    'data_class'=>null,
                    'required'=>true,
                ))
                ->add('isActif', null, array('required'=>false));
    }

    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults([
            'data_class'=>Document::class,
            'data_route'=>null,
        ]);
    }
}

而且我的 getter 和 setter 没有类型提示来确保不会调用 UploadedFile::__toString()

实体

class Document {
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;
    /**
     * @ORM\Column(type="string", length=100)
     */
    private $nom;
    /**
     * @ORM\Column(type="string", length=40)
     */
    private $fichier;
    /**
     * @ORM\Column(type="boolean")
     */
    private $isActif;
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Salle", inversedBy="documents")
     * @ORM\JoinColumn(onDelete="CASCADE")
     */
    private $salle;
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Stand", inversedBy="documents")
     * @ORM\JoinColumn(onDelete="CASCADE")
     */
    private $stand;

    public function __construct() {
        $this->isActif=true;
    }

    public function __toString() {
        return $this->getNom();
    }

    public function getId(): ?int {
        return $this->id;
    }

    public function getNom(): ?string {
        return $this->nom;
    }

    public function setNom(string $nom): self {
        $this->nom=$nom;

        return $this;
    }

    public function getFichier()/*Removed type hint*/ {
        return $this->fichier;
    }

    public function setFichier(/*Removed type hint*/$fichier): self {
        $this->fichier=$fichier;

        return $this;
    }

    public function getIsActif(): ?bool {
        return $this->isActif;
    }

    public function setIsActif(bool $isActif): self {
        $this->isActif=$isActif;

        return $this;
    }

    public function getSalle(): ?Salle {
        return $this->salle;
    }

    public function setSalle(?Salle $salle): self {
        $this->salle=$salle;

        return $this;
    }

    public function getStand(): ?Stand {
        return $this->stand;
    }

    public function setStand(?Stand $stand): self {
        $this->stand=$stand;

        return $this;
    }
}

然而,表单验证器仍然期待 string 而不是 UploadedFile 对象。

控制器

/**
 * @Route("/dashboard/documents/new", name="document_new", methods={"POST"})
 * @Route("/dashboard/hall-{id}/documents/new", name="hall_document_new", methods={"POST"})
 * @Route("/dashboard/stand-{id}/documents/new", name="stand_document_new", methods={"POST"})
 * @param Router $router
 * @param Request $request
 * @param FileUploader $fileUploader
 * @param SalleRepository $salleRepository
 * @param Salle|null $salle
 * @param Stand|null $stand
 * @return JsonResponse
 * @throws Exception
 */
public function new(Router $router, Request $request, FileUploader $fileUploader, SalleRepository $salleRepository, Salle $salle=null, Stand $stand=null) {
    if($this->isGranted('ROLE_ORGANISATEUR')) {
        $route=$router->match($request->getPathInfo())['_route'];
        if(($route == 'hall_document_new' && !$salle) || ($route == 'stand_document_new' && !$stand)) {
            //ToDo [SP] set message
            return $this->json(array(
                'messageInfo'=>array(
                    array(
                        'message'=>'',
                        'type'=>'error',
                        'length'=>'',
                    )
                )
            ));
        }

        $document=new Document();
        if($route == 'hall_document_new') {
            $action=$this->generateUrl($route, array('id'=>$salle->getId()));
        } elseif($route == 'stand_document_new') {
            $action=$this->generateUrl($route, array('id'=>$stand->getId()));
        } else {
            $action=$this->generateUrl($route);
        }
        $form=$this->createForm(DocumentType::class, $document, array(
            'action'=>$action,
            'method'=>'POST',
            'data_route'=>$route,
        ));

        $form->handleRequest($request);
        if($form->isSubmitted()) {
            //Fail here, excepting a string value (shouldn't), got UploadedFile object
            if($form->isValid()) {
                if($route == 'hall_document_new') {
                    $document->setSalle($salle);
                } elseif($route == 'stand_document_new') {
                    $document->setStand($stand);
                } else {
                    $accueil=$salleRepository->findOneBy(array('isAccueil'=>true));
                    if($accueil) {
                        $document->setSalle($accueil);
                    } else {
                        //ToDo [SP] set message
                        return $this->json(array(
                            'messageInfo'=>array(
                                array(
                                    'message'=>'',
                                    'type'=>'',
                                    'length'=>'',
                                )
                            )
                        ));
                    }
                }

                /** @noinspection PhpParamsInspection */
                $filename=$fileUploader->uploadDocument($document->getFichier());
                if($filename) {
                    $document->setFichier($filename);
                } else {
                    //ToDo [SP] set message
                    return $this->json(array(
                        'messageInfo'=>array(
                            array(
                                'message'=>'',
                                'type'=>'error',
                                'length'=>'',
                            )
                        )
                    ));
                }

                $entityManager=$this->getDoctrine()->getManager();
                $entityManager->persist($document);
                $entityManager->flush();

                return $this->json(array(
                    'modal'=>array(
                        'action'=>'unload',
                        'modal'=>'mdcDialog',
                        'content'=>null,
                    )
                ));
            } else {
                //ToDo [SP] Hide error message
                return $this->json($form->getErrors(true, true));
                // return $this->json(false);
            }
        }

        return $this->json(array(
            'modal'=>array(
                'action'=>'load',
                'modal'=>'mdcDialog',
                'content'=>$this->renderView('salon/dashboard/document/new.html.twig', array(
                    'salle'=>$salle,
                    'stand'=>$stand,
                    'document'=>$document,
                    'form'=>$form->createView(),
                )),
            )
        ));
    } else {
        return $this->json(false);
    }
}

services.yaml

parameters:
    locale: 'en'
    app_locales: en|fr
    ul_document_path: '%kernel.root_dir%/../public/upload/document/'

services:
    _defaults:
        autowire: true
        autoconfigure: true
        bind:
            $locales: '%app_locales%'
            $defaultLocale: '%locale%'
            $router: '@router'

    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    App\Listener\kernelListener:
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
            - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }
            - { name: kernel.event_listener, event: kernel.exception, method: onKernelException }

    App\Service\FileUploader:
        arguments:
            $ulDocumentPath: '%ul_document_path%'

【问题讨论】:

  • 我现在遇到了这个确切的问题,类似于 OP 的代码
  • 请向我们展示处理来自 config/services.yaml 的表单和参数的控制器,谢谢。
  • 更新了@ArleighHix
  • App\Service\FileUploader 也请
  • @ArleighHix 除了生成文件名的函数和将文件移动到正确文件夹中的函数外,什么都没有。此问题不需要,该错误甚至在调用 FileUploader 服务中的某个函数之前就发生了。

标签: symfony symfony4 symfony-forms


【解决方案1】:

config/packages/validator.yaml 中,如果这些行存在,请将它们注释掉:

framework:
    validation:
        # Enables validator auto-mapping support.
        # For instance, basic validation constraints will be inferred from Doctrine's metadata.
        #auto_mapping:
        #  App\Entity\: []

请参阅 Symfony 4.3 问题 [Validation] Activate auto-mapped validation via an annotation #32070

【讨论】:

  • 帮我修好了 :)
  • 它正在编辑中,我的问题在于创建。 Assert 就是这样......阅读更多关于 Assert 的信息,它不符合我的需求。另外,我不限于一种类型的文件。最后,您可以在我的 formType 中看到'data_class'=>null,。这是为了避免在编辑时重新创建文件对象的问题。
  • 澄清一下,我的问题是,虽然我确实在我的表单构建器中设置了FileType,但它仍然除了string,不应该是这种情况。
  • 我从我的代码中删除了 Assert 注释,它仍然有效。我以为我在遇到异常后最初添加了Assert\Type(type="File"。抱歉,我不知道它是如何工作的。我不知道为什么注释似乎解决了@Martijn 的问题。
  • 很奇怪。我唯一的改变是添加资产。清除缓存后,它仍然有效吗?
【解决方案2】:

在表单构建器中,将 data_class 设置为 null

->add('fichier', FileType::class, array(
    'data_class'=>null,
    'required'=>true,
))

FileType 实际上希望在内部定义一些数据类。它有一些动态定义类的逻辑:Symfony\Component\HttpFoundation\File\File 用于单个文件上传或null 用于多个文件。

因此,您实际上是在强制文件控件为多文件,但目标字段类型为 string。 Symfony 会进行一些类型猜测并相应地选择控件(例如,布尔实体字段将由复选框表示)——如果您没有指定明确的控件类型和选项。

所以,我认为您应该从您的选项中删除 data_class,这将解决问题。

这是一个指向特定位置的链接,使其表现得像我描述的那样:https://github.com/symfony/form/blob/master/Extension/Core/Type/FileType.php#L114

如您所见,它决定data_class 值和其他一些值,然后决定setDefaults(),即这些正确的值在那里——除非您覆盖它们。我会说有点脆弱的架构,但这是我们必须使用的。

【讨论】:

  • 我做了一个最小的项目来重现您的问题,但它确实有效(实际上,data_class 不会影响结果,使我的答案不正确,required 也无关紧要)。但同样,它只是工作。我只有两条线索 ATM: 1) data_route -- 它是什么?你的表单扩展?一些标准组件?标准 FormType 没有这样的选项; 2)action——你确定它指向这个控制器吗?总而言之,我很高兴分享我的示例项目,尽管它看起来很像 symfony.com/doc/current/controller/upload_file.html 和你的类名。
  • data_route 只是一个字符串,我用它来知道我从哪里来,这样我就可以调整表单(编辑时我不会有相同的字段)。 action 是对的,它使用了可用于此控制器的 3 条路由中的一条(我更新了我的代码)
  • 好吧,不是这样。同样,我的示例项目运行良好,它的代码看起来与你的很接近。还有一些问题:1)您使用的是什么 symfony 版本? 2)您的项目中是否有任何自定义表单扩展类? 3) DocumentType 中有任何模型映射器或数据转换器? 4) 表单或表单域的任何特殊选项,如inherit_datamapped 等?
  • 1) Symfony 4(已标记),2) 没有,3) 没有,4) 没有
  • 另一个可能的线索:“这个值应该是类型”字符串只存在于symfony/validator源中。因此,除非您在代码中的某处添加了这个确切的字符串,否则这来自某种约束。也许在你的代码中寻找“约束”命名空间的使用?
猜你喜欢
  • 2020-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-19
  • 2018-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多