Форма Symfony: Загруженный файл — «Это значение должно быть строкового типа»

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

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

This value should be of type 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.

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%'

У меня сейчас точно такая же проблема, код похож на OP

Martijn 20.06.2019 19:51

Пожалуйста, покажите нам контроллер, который обрабатывает форму и параметры из config/services.yaml, спасибо.

Arleigh Hix 21.06.2019 23:05

обновлен @ArleighHix

Preciel 22.06.2019 01:29

И App\Service\FileUploader тоже пожалуйста

Arleigh Hix 22.06.2019 05:03

@ArleighHix ничего особенного, кроме функции для создания имени файла и функции для перемещения файла в нужную папку. Не требуется для этого вопроса, ошибка возникает еще до вызова одной из функций в службе FileUploader.

Preciel 22.06.2019 09:16

почему эта строка 'data_class'=>null в типе формы?

Arleigh Hix 22.06.2019 11:33

@ArleighHix При загрузке формы для редактирования уже сохраненного элемента форма по-прежнему ожидает экземпляр File. Вы, конечно, можете восстановить файл с помощью new File(), но это не обязательно. Установка data_class на null сделает так, что вам не нужно регенерировать файл при редактировании.

Preciel 22.06.2019 21:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Symfony Station Communiqué - 17 февраля 2023 г
Symfony Station Communiqué - 17 февраля 2023 г
Это коммюнике первоначально появилось на Symfony Station , вашем источнике передовых новостей Symfony, PHP и кибербезопасности.
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
8
7
4 817
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В 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..

Исправил это для меня :)

Martijn 20.06.2019 22:04

Это редактируется, моя проблема заключается в создании. И Assert то, что есть... Подробнее о Assert, это не соответствует моим потребностям. Кроме того, я не ограничен одним типом файла. Наконец, вы можете увидеть 'data_class'=>null, в моем formType. Это сделано для того, чтобы избежать проблемы воссоздания файлового объекта при редактировании.

Preciel 20.06.2019 22:15

Чтобы уточнить, вот моя проблема, хотя я установил FileType в своем конструкторе форм, он все еще исключает string, чего не должно быть.

Preciel 20.06.2019 22:25

Я удалил аннотации Assert из своего кода, и он все еще работает. Я думал, что изначально добавил Assert\Type(type = "File" после получения исключения. Извините, я не знаю, как это работает. Я не знаю, почему аннотации, по-видимому, исправили проблему для @Martijn.

Brent Pfister 22.06.2019 13:02

Странный. Моим единственным изменением было добавление активов. После того, как вы очистите кеш, он все еще работает?

Martijn 22.06.2019 15:32

@Martijn Я очистил кеш с помощью php bin/console cache:clear и обновил браузер, и загрузка файлов по-прежнему работает без аннотаций Assert. Я использую Symfony 4.3.1 и PHP v7.2.19-0ubuntu0.18.04.1. Какие версии вы используете? Не могли бы вы попробовать удалить Asserts из своей сущности и проверить, повторяется ли исключение?

Brent Pfister 22.06.2019 17:35

Я не получаю «должна быть строка», когда удаляю ее, все работает, но ничего не делает :) S4.3.1, php7.2.4, windows10 через WAMP

Martijn 22.06.2019 19:36

@Preciel Несмотря на то, что это не кажется необходимым и не соответствует вашим потребностям, не могли бы вы добавить аннотации Assert, как описано в моем ответе выше? Исключение все еще происходит? Если нет, попробуйте удалить эти аннотации Assert и посмотрите, произойдет ли исключение?

Brent Pfister 23.06.2019 06:04

Пробовал, проблема не меняется, ошибка все равно есть. Каким-то образом, чтобы быть исключенным... Assert (Constraints) предназначен для утверждения, проверки, проверки и т. д.... Не для преобразования. Не знаю, как это решило проблему @Martijn...

Preciel 23.06.2019 07:15

@Preciel Содержит ли ваш config/packages/validator.yaml строки auto_mapping, как в моем отредактированном ответе? Мне удалось воспроизвести ваше исключение This value should be of type string, добавив эти строки в мой validator.yaml и удалив аннотации Assert на $fichier.

Brent Pfister 26.06.2019 01:16

Хорошая находка. Удалил валидатор auto_mapping и теперь он работает. Вы должны очистить свой ответ, чтобы оставить только часть о auto_mapping (и ссылки на источники). Я думаю, вы неправильно понимаете использование Assert... ;)

Preciel 26.06.2019 04:14

@Preciel Очищенный ответ в соответствии с вашим направлением.

Brent Pfister 26.06.2019 14:09

В конструкторе форм вы устанавливаете 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 также не имеет значения). Но опять же, это просто работает. У меня только две подсказки банкомата: 1) data_route -- что это такое? ваше расширение формы? какой-то стандартный компонент? стандартный FormType не имеет такой опции; 2) action -- вы уверены, что он указывает именно на этот контроллер? В общем, я рад поделиться своим примером проекта, хотя он будет очень похож на symfony.com/doc/current/controller/upload_file.html с вашими именами классов.

alx 24.06.2019 14:12
data_route — это просто строка, я использую ее, чтобы узнать, откуда я пришел, чтобы я мог настроить форму (у меня не будет одинаковых полей при редактировании). action правильно, он использует один из 3 маршрутов, доступных для этого контроллера (я обновил свой код)
Preciel 24.06.2019 14:27

Хорошо, тогда это не так. Опять же, мой пример проекта работает нормально, и его код похож на ваш. Еще несколько вопросов: 1) какую версию symfony вы используете? 2) есть ли в ваших проектах какие-либо пользовательские классы расширения формы? 3) какие-нибудь преобразователи моделей или преобразователи данных в вашем DocumentType? 4) какие-либо специальные параметры в формах или полях форм, например inherit_data, mapped и т. д.?

alx 24.06.2019 14:46

1) Symfony 4 (с тегами), 2) нет, 3) нет, 4) нет

Preciel 24.06.2019 15:31

Можете ли вы указать точную версию Symfony, например вывод из `composer show | grep symfony/framework` (для меня это будет 4.3.1). Понижение до чего-то вроде 4.0 было бы адской задачей...

alx 24.06.2019 15:55

Я также использую Symfony 4.3.1, обновленную прошлой ночью, хотя это может быть проблема с самой Symfony.

Preciel 24.06.2019 16:00

Итак, я воспроизвел ваш пример как можно ближе (учитывая весь код, который я вижу в вашем вопросе), и он отлично работает. Видимо проблема в частях, которые я не вижу? Других идей пока нет, извините.

alx 24.06.2019 16:11

Я просто "откатюсь" и попробую еще раз, может что-то упустил. Нечего особо не показать, сам проект достаточно свежий, сделано не много. Может просто не повезло.

Preciel 24.06.2019 16:30

Если он не большой/чувствительный, не могли бы вы как-нибудь поделиться полным кодом?

alx 24.06.2019 16:33

Обновил мой вопрос. Дайте мне знать, если вам нужно что-то еще.

Preciel 24.06.2019 20:45

Я сделал еще один тест с нуля, не генерировал объекты и т. д., но вставил ваш код (закомментировал некоторые несвязанные вещи, такие как SalleRepository), но он работает нормально. (Ваш код, кажется, из разных версий вашего приложения, поскольку DocumentType относится к полям, которых нет в документе).

alx 24.06.2019 21:08

Единственная заметная разница, о которой я мог подумать, заключается в следующем: это действие контроллера принимает POST, но я не вижу, как создается ваша начальная форма. TBH Я не понимаю, как это может вызвать проблемы, но технически мы проверяли только приемную часть. Возможно, это даст вам некоторые подсказки.

alx 24.06.2019 21:10

Еще одна возможная подсказка: строка «Это значение должно быть типа» существует только в symfony/validator источниках. Итак, если вы не добавили именно эту строку где-то в свой код, это происходит из-за некоторого ограничения. Может быть, искать использование пространства имен «Ограничение» в вашем коде?

alx 24.06.2019 21:12

Извините за несоответствие формы, просто я убрал неактуальные параметры. Насчет моей формы, она основная form_start, form_widget, form_end. Я еще не редактировал эту часть. И единственное «Ограничение», которое у меня есть, находится в пользовательском объекте (уникальный адрес электронной почты). Я вообще в растерянности... :/

Preciel 24.06.2019 23:32

Дайте мне знать, если вы решите поделиться своим проектом (публично или лично со мной).

alx 24.06.2019 23:40

Другие вопросы по теме