Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony form: Uploaded file - "This value should be of type string"

[UPDATED]: 2019/06/24 - 23;28

Uploading a file with a form, I encounter the following error:

This value should be of type string

The form builder is set to FileType as it should:

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,
        ]);
    }
}

And my getter and setter have no type hint to make sure that UploadedFile::__toString() won't be invoked:

Entity

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;
    }
}

Yet, form validator is still expecting a string and not an UploadedFile object.

Controller

/**
 * @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%'
like image 868
Preciel Avatar asked Jun 20 '19 17:06

Preciel


2 Answers

In config/packages/validator.yaml comment out these lines if they exist:

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

See Symfony 4.3 issue [Validation] Activate auto-mapped validation via an annotation #32070.

like image 88
Brent Pfister Avatar answered Sep 23 '22 12:09

Brent Pfister


In your form builder, you set data_class to null:

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

But FileType actually expects some data class to be defined internally. It has some logic to define class dynamically: it is either Symfony\Component\HttpFoundation\File\File for single file upload or null for multiple files.

So, you're effectively force your file control to be multifile, but target field type is string. Symfony does some type-guessing and chooses controls accordingly (e.g. boolean entity field will be represented by checkbox) -- if you don't specify explicit control type and options.

So, I think you should remove data_class from your options, and that will solve the problem.

Here is a link to specific place to make it behave like I have described: https://github.com/symfony/form/blob/master/Extension/Core/Type/FileType.php#L114

As you can see, it decides on data_class value and some other values, then does setDefaults(), i.e. these correct values are there -- unless you override them. A bit fragile architecture, I'd say, but that's what we have to work with.

like image 45
alx Avatar answered Sep 23 '22 12:09

alx