[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%'
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With