Populate a second entity after a form submit in Symfony2 - php

I'm recently new Symfony (2.8) and I'm trying, that after an image file submission from a form, is to populate a second entity with the mains colors of the image.
For now, I just made a test from the controller like this:
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($image);
$extractor->setImage($image->getFile());
$palette = $extractor->extractPalette();
foreach($palette as $color => $weight){
$imageColor = new Color();
$imageColor->setImage($image);
$imageColor->setRgb($color);
$imageColor->setWeight($weight);
$em->persist($imageColor);
}
$em->flush();
It's working but I don't think that the colors should be in the form or the controller neither is the image entity.
So, how should I deal with the colors ?

I have two solutions in mind to save Colors outside your controller:
Create a Doctrine listener (on prePersist and preUpdate) that will generate Colors when an Image is persisted.
Before flush, dispatch a custom Event and catch it with an EventListener. When caught, execute method that populate Colors.
Advantage of solution 1: Colors will automatically be generated for an Image, whether you are in a Controller, a Command, or somewhere else.
Advantage of solution 2: You can decide more precisely when you want to generate Colors, by dispatching your custom event.

Related

symfony3: form action not working (and happy for it)

I'm making a fairly large symfony3 application. It's my first one and I must say I'm pretty amazed by the robustness of the framework. It's an application that lets users create "events". And every event is happening in a certain "location". I have two bundles "EventBundle" and "VenueBundle". A venue can host many events so there's a one to many relation between the two. At this point I have an event creation page with a dropdown input that is automaticly filled with the venues already in the database. The EntityType field made it easy for me to implement that relation. This is an awesome symfony feature.
Because it's possible that not every venue is already in the database at the moment of creating a new event, I want to use a small "quick create venue" modal window (based on zurb-foundation) in the event creation wizard. It's to prevent users from exiting the wizard to add a new venue.
I was struggeling for the past two or three days or so to have two forms from different entities on one twig page. I have already found the answer in this question: Symfony - Two forms from two entities on the same page. But the answer raised a second question for me. I think it's easier to explain if I first show the code I'm having right now:
public function createAction(Request $request)
{
$event = new Event();
$eventForm = $this->createForm(EventType::class, $event);
$venue = new Venue();
$venueQuickForm = $this->createForm(VenueQuickType::class, $venue, array(
'action' => $this->generateUrl('massiv_venue_quickcreate')
));
$eventForm->handleRequest($request);
if($eventForm->isSubmitted() && $eventForm->isValid()) {
$event = $eventForm->getData();
$event->setPostedBy(1);
$event->setUpdatedBy(1);
$em = $this->getDoctrine()->getManager();
$em->persist($event);
$em->flush();
return $this->redirectToRoute('massiv_event_homepage');
}
$venueQuickForm->handleRequest($request);
if($venueQuickForm->isSubmitted() && $venueQuickForm->isValid()) {
$venue = $venueQuickForm->getData();
$venue->setPostedBy(1);
$venue->setUpdatedBy(1);
$em = $this->getDoctrine()->getManager();
$em->persist($venue);
$em->flush();
}
In the createForm method of the second form (venueQuickForm) I added the option "action" with a url pointing to the venue controller's quickcreate action. That was my idea, I had this already in my code before I found the answer, but kept that line to see how it would behave. It turns out it is ignored because in that quickCreateAction method I simply put a "ok" response and that page is not shown when a press the submit button. The rest of the code works fine, the venue is saved in the database.
So I am about to delete that line, but is the code above indeed the way to go? Intuitively I want to keep both codes seperate so putting the "save venue" part in the Venue controller seems locic to me or is that not the way Symfony is designed to work?

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.

Symfony2 : Adding / updating entity in PUT route

I have a PUT route that updates a user entity with the request data. This works but I am having to call setters on this entity, but I am thinking there might be a better way, where the request data can be applied directly without calling setters for each field. I am using Doctrine. Here is my sample code:
$data = $this->getRequest()->request->all();
$em = $this->getDoctrine()->getManager();
$entity = new Entity();
$entity = $em->getRepository('SomeBundle:Entity')->find($id);
$entity->setParamA($data['A']);
// ... etc
if (!empty($entity))
{
$em->persist($entity) // flush, etc. I have also tried merge but it removes existing fields
}
How can I simply update $entity with the $data from request without calling setters? If I have a hundred fields, it doesn't make sense to manually call setters, what if there were even more fields?

Dynamic form (switch entity) symfony2

I'm making a page for adverts. An advert can be of different types and therefore have different data. For instance, a vehicle would have the make and the model as extra data.
Right now, I've got one base doctrine entity Advert which contains the data that every advert requires. Different adverts in turn innherits this data (doctrine2 discriminatormap)
I need to populate the form dynamically (with ajax and symfony2 forms) if the user choose to create a vehicle ad I want to display the options for a vehicle advert. But I also need to change the entity to be of the form AdvertVehicle.
Is this possible? I did read the cookbook entry at the symfony2 homepage
"How to Dynamically Modify Forms Using Form Events":
This should be handled by making an AJAX call back to your application. In that controller, you can submit your form, but instead of processing it, simply use the submitted form to render the updated fields. The response from the AJAX call can then be used to update the view.
I understand how to make an ajax call back to my controller, and i understand how to use the form-events but how do I get the response of a rendered select-box (containing vehicle models for instance) back? With a new AbstractType? or formbuilder?
And then when the user actually submits the form I need to use the entity of the selected advert type. Can I change the entity according to the users choice in the form dynamically?
Edit
I checked the form innheritance out that's great, thank you. I extend the AdvertType and override the buildForm() method and before I add the items I need for the AdvertVehicleType I call the parent method.
Futher Explanation
Every advert entity contains price, description, title and category. Some adverts contains more, such as make and model. They are differentiated by the discriminatormap (doctrine2)
Example:
// -- Entity
class CarAdvert extends Advert {
protected $model;
protected $make;
}
// -- Entity
// -- This uses discriminator mapping
class Advert {
protected $title;
protected $description;
protected $price;
protected $category;
}
if the user selects the category cars I want to use the CarAdvert entity (for validation and persistance) if the user selects the house hold itemcategory I just want to use the normal Advert entity.
One major problem is still that I cannot figure out how to render the extended form via ajax. Any tips on this part? When the user selects car as a category, I want the form to be updated (via jQuery/ajax) but how do I make a controller that retrieves just the extended part of the form and sends the html back as a response (without using twig and rendering it in a view, is this possible)?
Solution:
See answer below!
Solution:
The solution to my problem was to create a few extra functions in the controller to solve the issue where I want to be able to change the entity and form "on the fly" from a selection by the user..
public function indexAction(Request $request)
{
$form = $this->getForm($request);
$form->handleRequest($request);
return array(
'form' => $form->createView(),
'request' => $request->request,
);
}
Where getForm retrieves the form, (e.g AdvertVehicleType for vehicles or AdvertType for a "default" advert).
The getForm method looks like this:
private function getForm(Request $request)
{
$categoryTitle = 'NONE';
$categoryId = $request->request->get('advert', false)['category'];
if ($categoryId) {
$categoryTitle = $this->getDoctrine()->getRepository('Bundle:Category')->find($categoryId)->getTitle();
}
return $this->createForm($this->getFormType($categoryTitle), $this->getEntity($categoryTitle));
}
here I retrieve the categoryID (that is selected in the form in the request) and retreives the formType with getFormTypeand the entity with getEntity.
private function getEntity($categoryTitle)
{
$entity = new Advert();
switch ($categoryTitle) {
case Category::CARS:
$entity = new AdvertCar();
}
return $entity;
}
private function getFormType($categoryTitle)
{
switch ($categoryTitle) {
case Category::CARS:
return new AdvertCarType();
default:
return new AdvertType();
}
}
To be able to update this "on the fly" with ajax (but it also works if the user tries to submit the form) I created another action in the controller.
This action renders the parts of the form that I want to update (on ajax call), I do this by actually picking out what I don't need in the form with twig setting the form objects to rendered like so:
{% do form.title.setRendered %}
(this is just an example I actually do this for all the form objects that I don't want to render.
I then simply just call:
{{ form_rest(form) }}
which will retrieve the "rest" of the form which is different for different categories.
Now let's say you have state and than town to select. First select the state then you render the towns for that state in twig (but then you can actually just render the part you need, e.g {{ form_row(form.towns) }} and you return this rendered template as a json-response and just put it in the div you want with jquery.
$html = $this->renderView('#Bundle/NewAddPage/filter_area.twig', array('form' => $form->createView()));
and then returning the $html variable in the response.
I hope this helps, and that the explanation is good enough, if not just make a comment and I'll update this with my answer!

Doctrine (Mongo) Persisting Partial Document

I have a custom class that populates a controller's action parameters based on the typehint of the parameter. This works well for documents (using public properties and setters).
My aim is to make the controller simple:
function updateAction(Article $article)
{
$dm = new DocumentManager(); // code elsewhere
$dm->merge($article);
$dm->flush();
return $this->redirect('/article/' . $article->getId());
}
The problem is that the input supplying the fields to programatically populate the Article class doesn't contain all of the properties of an Article class (perhaps the edit form only contains Title and Content, but disregards Author, etc).
I was hoping that the presence of an ID would allow the document to be merged gracefully with what is currently in the database. However, any fields that are missing at the time of a merge will be removed from the document in the database.
Is there a way to update a document in such a way that only the fields that are present (non-null, I guess) are updated?
Rather than hitting the db twice - once for the find, and once for the update, you can use a FIND_AND_UPDATE query.and do it all in one step.
See this docs page for details: http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/find-and-update.html
It seems that a clean way would be to bind the model AFTER retrieving it from the database. Something along the lines of ASP.NET MVC's UpdateModel.
function updateAction($id)
{
$dm = new DocumentManager(); // code elsewhere
$article = $dm->getRepository('Article')->find($id);
$this->updateModel($article);
$dm->flush();
return $this->redirect('/article/' . $article->getId());
}
If there are any better suggestions, feel free to answer...

Categories