[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