[更新]:2019/06/24-23; 28
使用表单上传文件时,遇到以下错误:
此值应为字符串类型
表单生成器应设置为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,
]);
}
}
我的getter和setter没有类型提示,以确保不会调用UploadedFile::__toString()
:
实体
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%'
答案 0 :(得分:4)
在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版本[Validation] Activate auto-mapped validation via an annotation #32070。
答案 1 :(得分:1)
在表单构建器中,将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()
,即这些正确的值就存在了-除非您覆盖它们。我会说架构有点脆弱,但这就是我们必须使用的架构。