symfony form with new files via Vich Uploader - php

Iam trying to build a smarter upload structure for my app. until now I had the code for uploading files in the controller and everything was more manual. Now I want to use the VichUploader in Symfony, but I have problems to implement it with multiple files.
First, I dont have an Entity with a file but an Entity that holds multiple File Entities. To be more clearly: The Entity Document has a oneToMany relation to File. So I build a form with a CollectionType:
$builder->add('files', Type\CollectionType::class, [
'entry_type' => Type\FileType::class
])
But because there is not file yet when I add a new Document, no upload field is shown. And even when there are already files (on edit form), there shouldn't be upload fields shown but text fields with file names.
How can I achieve that? Do i still need to add an umapped field files_new() with a multiple FileType? Then the VichUploader automatic stuff would not work.

If you pass an empty file object you should have your html fil input as it is a collection
In your controller (I don't have your code)
$entity = new Entity()
$entity->addFile(new FileEntity())//Add an empty file object
$form = $this->createForm(YourType::class, $entity);
$form->handleRequest($request);

Related

Symfony2 Validating submitted data against Model class

I'm trying to validate submitted data against existing Model/Entity/POPO, however I can't get it to work in any simple way.
All of this is takes place inside a controller action.
So, I can do like this:
$constraints = new Assert\Collection([
'username' => [new Assert\NotBlank()],
'email' => [new Assert\Email()],
]);
$violationList = $this->get('validator')->validate($request->request->all(), $constraints);
However to do that in every action makes no sense, as having all constraints in a class would be a lot better. So, Validation component allows to do like this:
// All constraints are defined inside Customer class
$customer = new Customer();
$violationList = $this->get('validator')->validate($customer);
Violation list is full of errors now, as $customer is an empty object, but the problem is I can't find a way to use data from POST AND validate it against constraints that are defined in the class.
It is possible to write extra component/helper that would take POST data and then will call bunch of ->setUsername(), ->setEmail(), etc., but that doesn't seem right considering you can easily map Model to POST data, if:
Form component is involved;
OR using ConstraintsCollection manually;
Am I missing something obvious here or there is no out-of-the-box possibility? Thanks!
AFAIK the form component is the one responsible for mapping post data to your entity. So you have two choices
Use a form, like that you will have your data mapped and your model validated
Skip the form but then you have to map request params to your entity manually. then validate your model with $this->get('validator')->validate($customer);
Edit :
The form role is to map data coming from request ( html form , api .... ) to a model. Validation could be done with from or without it as its the validator component who does the job , it should be noted that the validation is done on the model and not the form.
If you want to skip the form check this question: Populate entity from data array without form/request although the form component is very useful specially if you are using the same logic in many places ( create / edit .. )

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

Create form for Uploadable Doctrine Extension

I'd like to use Uploadable to save some images (i.e. profile picture for users). I'm using many other Doctrine Extensions already (softdeletable, timestampable, blameable, etc.) so I thought it would be nice to use this one as well.
However, I don't know how to set up my Forms. The StofDoctrineExtensionsBundle documentation gives this example:
$document = new Document();
$form = $this->createFormBuilder($document)
->add('name')
->add('myFile')
->getForm()
;
//(...)
$uploadableManager->markEntityToUpload($document, $document->getMyFile());
In this example, is name the name of the Document or the name of the file?
Atlantic18/DoctrineExtensions's documentation adds a path, name, mimeType and size to an entity, to there is no myFile attribute.
Can anybody explain how to set up a Form for a Uploadable Doctrine entity? I couldn't find any documentation or good example that helped me further.
Entity
Like you've discovered, the documentation of DoctrineExtensions itself sheds some light on how to configure the Uploadable extension to use your entity.
Mainly by adding the #Gedmo\Uploadable annotation to your entity and #Gedmo\UploadableFilePath to the property that will contain the file path ($filePath for example).
Form
With the ->add() method of the form-builder you add fields to the form. The first parameter specifies the property-name of the corresponding entity. So ->add('myFile') would add a field for the property $myFile.
What you need to do is add a (file) field to the form for the property that will contain the file path ($filePath for example), and mark that property:
$form = $this->createFormBuilder($entity)
->add('filePath');
$uploadableManager->markEntityToUpload($entity, $entity->getFilePath());
In other words: myFile in your example should be replaced with filePath in my example, and whatever the actual property is in your real code.

SonataMediaBundle - How to use custom file input

I have a kind of scavenger hunt project in which I am using AngularJS to manage the different questions it may contain. These questions are of different types. Therefore, some may include a file input and some may not. In this project, I am also using Symfony and SonataMediaBundle to manage my files and my images.
Since my html model (mostly my forms) can change depending on the actions of the user, I cannot use Symfony's built-in tool to produce forms. Therefore, all my forms are custom made. This gives me a problem with SonataMediaBundle, when I want some files to be uploaded. If a user selects a file, this file will be sent via POST to a method in the controller, when the form gets sent. Therefore, I want to send this received file to SonataMediaBundle so that it can manage it, but I haven't found anywhere in the documentation how to do such a thing.
Theoretically, it is really simple. In my controller, when I get a file input, I want to let SonataMedia manage the upload (that is the copy to the proper location, etc...) and I have no clue on how I should do that.
Using symfony2 and not utilizing its benefits you are doing a big mistake you should built your app properly but as far as concerned to your question nothing is bounded by symfony but its on your own how you use it.You can get the sonata media manager service from the container and you have to manually set the all the required setters for the media manager and you have to manually work for the validations like file size ,file mimetype etc. Below is the demo how you can store the file in the sonata media bundle
/* Before class use these*/
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Application\Sonata\MediaBundle\Entity\Media;
public function uploadAction()
{
$file = $this->get('request')->files->get('filefieldname');
if (!$file instanceof UploadedFile || !$file->isValid()) {
return new Response(json_encode(array(
'event' => 'uploader:error',
'data' => array(
'message' => 'Missing file.',
),
)));
}
/* validate max min size for the file */
/* validate mime type for the file */
/* Get sonata media manager service from container */
$mediaManager = $this->container->get('sonata.media.manager.media');
/* create new instance of sonata media class in my case entity is located at
* Application\Sonata\MediaBundle\Entity\Media
*/
$media = new Media();
$media->setBinaryContent($file);
$media->setContext('default');
$ImagemimeTypes = array('image/jpeg', 'image/png');
$FilemimeTypes = array('application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/msword', 'application/pdf', 'application/x-pdf');
if (in_array($file->getMimeType(), $FilemimeTypes)) {
$media->setProviderName('sonata.media.provider.file');
}
if (in_array($file->getMimeType(), $ImagemimeTypes)) {
$media->setProviderName('sonata.media.provider.image');
}
/*If you have other providers in your app like vimeo /dailymotion etc
then do set here i have shown you the demo for adding image/file in sonata media */
/*other setters if you want to set like enabled/disable etc*/
$mediaManager->save($media);
// end function
}
But once again there will be alot of rework you have to do which symfony already provides you the ease for
The Bundle is there to close exactly this gap between Symfony and SonataMedia. SonataMedia is made for raw PHP, while the SonataMediaBundle attaches Symfony interfaces to SonataMedia; you're rewriting large part of the Bundle's functionality. To get a good example of how it is done right, look at the Bundle's code ;)
Sorry if the next suggestion is something you considered thoroughly, I just cannot be certain based on the limited information in the question. Symfony forms are highly flexible and provide a lot of critical functionality that you should not try to re-implement yourself (like CSRF-tokens). Most problems have a good solution with the form system. If a form is metamorphic (has many constellation of fields, based on UI interaction) and cannot be handled by multiple form types, you still have options. If you can set a GET parameter indicating which type of form is currently being sent then you can pass that to the FormBuilder. You may even choose to build a single big form for the whole application, which contain every field you ever use - that's a bit of a waste but still better than trying to wire posting+sonata together. The most hurt the first version does are some extra bytes and empty field. The least hurt you try could do is introduce serious security leaks.

How to make FileUpload Object handle Multiple files in Symfony2?

I'm trying to make a form with a multiple file field. Since the docs are quite vague:
http://symfony.com/doc/current/reference/forms/types/file.html
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
I've decided to use W3C FileReader API (Based in the doc urls below), to handle files from the client and manage the underliying data from the view to the entity. Currently supports drag&drop, metadata, and multiple selections on the client.
http://www.html5rocks.com/en/tutorials/file/dndfiles/
http://playground.html5rocks.com/#reading_file_metadata
https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
But I want to give the UploadedFile object one more chance, and the MAIN problem I have is that I can't make the file property in my entity (FileUpload type) to store more than one file data. My input looks like this:
<input type="file" id="upload_files" name="upload[files][]" required="required" multiple="multiple" />
In theory If I made the name to be an Array, the fileUpload should contain the files but it doesn't. Can UploadedFile object store multiple files data ? or just single ?
http://api.symfony.com/2.2/Symfony/Component/HttpFoundation/File/UploadedFile.html
Also tried to initialize (in the __construct of the entity) the $files property as an array and modify the setFiles() to store new array index's $this->files[] = $file; ... you know.
But then Symfony tells me that exception:
The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) array. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) array to an instance of Symfony\Component\HttpFoundation\File\File.
I'm not familiarized with data transformers. And Can't figure it out how can be done now. Or If it could be really useful to get the UploadedFile Object with every file data.
In synthesis... With this given info, and the code below. Could anyone help me to get the FileUpload object the correct number of files and not just the last added ? Thank You
Made a repo on github: https://github.com/jeflopo/fileupload
For brevity, here are the relevant files:
The form:
https://github.com/jeflopo/fileupload/blob/master/src/Acme/DemoBundle/Form/Type/FileUploadType.php
The Entity:
https://github.com/jeflopo/fileupload/blob/master/src/Acme/DemoBundle/Entity/FileUpload.php
The controller (Just see the uploadAction):
https://github.com/jeflopo/fileupload/blob/master/src/Acme/DemoBundle/Controller/DemoController.php
The View:
https://github.com/jeflopo/fileupload/blob/master/src/Acme/DemoBundle/Resources/views/Demo/upload.html.twig
The JavaScript that handles the files on the client (Doesn't affect to files behaviour in the server):
https://github.com/jeflopo/fileupload/blob/master/src/Acme/DemoBundle/Resources/public/js/upload.js
I created a pull request which fixes the error!
What's left to do now is that you create unique filenames for the uploaded files and then use the move method on each file. If you don't move them the files won't get saved!
As I set mapped to false, your entity doesn't contain the files. You have to create an array with the filenames you just created to save the file paths.

Categories