Форма Symfony: загруженный файл - «Это значение должно иметь тип string» - PullRequest
6 голосов
/ 20 июня 2019

[ОБНОВЛЕНО]: 2019/06/24 - 23; 28

При загрузке файла с формой я сталкиваюсь со следующей ошибкой:

Это значение должно иметь тип string

Для конструктора форм установлено значение 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,
        ]);
    }
}

А у моего геттера и сеттера нет подсказки типа, чтобы убедиться, что UploadedFile::__toString() не будет вызван:

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

Тем не менее, средство проверки формы все еще ожидает 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%'

Ответы [ 2 ]

4 голосов
/ 20 июня 2019

В 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 [Валидация] Включите автоматическое сопоставление проверки с помощью аннотации # 32070 .

1 голос
/ 23 июня 2019

В построителе форм вы устанавливаете 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(), то есть эти правильные значения есть - если вы не переопределите их.Я бы сказал, что немного хрупкая архитектура, но это то, с чем мы должны работать.

...