Danil Pyatnitsev Danil Pyatnitsev - 3 months ago 128
PHP Question

Symfony 3 FileUpload

I'm trying implement file uploading functionality for my app with Symfony 3.
I have a product entiry, that have relation to File entiry.
Part of Product:

/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\File", mappedBy="product")
* @ORM\OrderBy({"weight" = "DESC"})
*/
protected $files;


and field on form:

->add('files', FileType::class, array('multiple'=> true, 'data_class'=> 'AppBundle\Entity\File'));

public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product',
));
}


As you can see, I'm set data_class.

and in controller I'm trying handle form

public function addAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$product = new Product();
$product->setAddedBy($this->getUser());
$form = $this->createForm(ProductType::class, null);
$form->handleRequest($request);
...


and I have an error:


Expected argument of type "AppBundle\Entity\File", "Symfony\Component\HttpFoundation\File\UploadedFile" given


If I drop data_class mapping I have no error and no object, just array.
How I can resolve this error, how to transform UploadedFile to File (Entiry). I'm trying to create Transformer, but I just got the ProductEntiry class, and as result can't process it, becouse it's without files.

Answer

Before I'll get to the point, just one suggest. In line:

$form = $this->createForm(ProductType::class, null);

I would provide $product variable so it will be automatically filled with data instead of creating new one. So it should be changed to :

$form = $this->createForm(ProductType::class, $product);

Ok, now, the problem occurs, because you probably have in your Product class a setter like:

public function addFile(AppBundle\Entity\File $file) { ... }

Then, after successful validation, the form tries to fill instance of Product class with data from the form, which contains Symfony's UploadedFile class instance. I hope you understand that.

Now, you have (at least) two possible solutions.

You can set "mapped" => false option for the file field. That will stop form from trying to put it's value into underlying object (Product instance).

After doing that you can handle the value on your own, which is handle file upload, create AppBundle/Entity/File instance and put it into $product variable via setter.

That the lazy solution, but if you would like to do the same in other forms, you will have to copy the code to every controller that needs it. So it's easier only for one time usage.

The right solution would be to convert UploadedFile to you File object with a Data Transformer. It's a longer topic to talk about and exact solution depends on your data flow that you want to achieve. Therefore if you want to do this right, read about Data Transformers in Symfony's docs first.

I promise that you will thank yourself later if you do that the right way. I've spent some time on understanding Symfony Form Component including Data Transformers and solved a similar issue that way. Now it pays back. I have reusable image upload form that handles even removing previously uploaded files in edit forms.

P.S. It's "entity", not "entiry". You've wrote "entiry" twice, so I'm just saying FYI.