How to apply only diff changes from request object to form - php

I'm implementing patch method using fosrestbundle and I want to create proper patch method.
To do so I've created controler and there is patchAction which takes an argument Entity, Entity is created passed via ParamConverter which I wrote myself. The Entity is passed to EntityType and here's the problem. I want to update only fields that changed and when I pass Entity to form it set nulls to object that comes from request. Entity is POPO
Here's the flow
User sends PATCH request to /entity/{Entity} let's say /entity/12
Param converter converts 12 to proper Entity asking DB for the data
EntityFormType takes Entity as argument and sets data from request to entity.
Entity is stored to DB
The problem is that form after it takes whole Entity object it sets null for fields that are null on form. I'd prefer if it took these values and set it for example as defaults.
I don't and can't use doctrine ORM.
The code:
/**
* #ParamConverter("Entity", class="Entity")
*/
public function patchAction(Entity $entity, Request $request)
{
var_dump($entity); // object mapped from DB
$form = $this->createForm(new EntityType(), $entity);
$form->handleRequest($request);
$form->submit($request);
var_dump($entity);exit; //here I get only values that i passed through patch method, rest of them is set to null
}
I was thinking about form events or creating something like diff method but probably there is better solution?

You need to create your form with method option set.
$form = $this->createForm(new EntityType(), $entity, array(
'method' => $request->getMethod(),
));
If request is send with PATH method then Symfony will update only sent fields.
How to fake PATCH method in Symfony: http://symfony.com/doc/current/cookbook/routing/method_parameters.html#faking-the-method-with-method

Related

Symfony 2.8 to 3.4: Simple array of entity IDs not saving

I'm updating an app from Symfony 2.8 to 3.4.
I used to store a collection of entities sending an AJAX request (not using Twig's form_xxx helpers)
This is a typical payload:
my_healthbundle_anamnesistype[habits][]: 5
my_healthbundle_anamnesistype[diseases][]: 9
my_healthbundle_anamnesistype[diseases][]: 10
These had no special treatment on the AnamnesisType form builder, no ChoiceType or EntityType, whatsoever, just:
$builder->add('habits')
->add('allergies')
->add('interventions')
->add('diseases')
All of these entities are super simple, just an ID and a Name field.
Each one of these is mapped in the Anamnesis entity as such:
/**
* #ORM\ManyToMany(targetEntity="Habits")
*/
protected $habits;
The controller code was:
$editForm = $this->createForm(new AnamnesisType(), $entity);
$request = $this->getRequest();
$editForm->bind($request);
if ($editForm->isValid()) {
/// WIN!
All of this worked like a charm in 2.8. As you can see this was super simple and straight forward. The form builder has no reference to any entity classes, choice type, entity types, nothing. By sending the mentioned payload and calling ->bind() all the "habits" inside an "Anamnesis" were replaced.
After updating the deprecated stuff and following all recommendations (getRequest() became a call to request_stack, bind() became handleRequest(), create form changed from "new AnamnesisType" to "AnamnesisType::class", etc), the isValid() method is returning false, and I'm getting an empty string when calling $editForm->getErrors(true, false)
Am I missing a step here? Did the way collections from a Request are saved into a form as entites changed? Any help is appreciated, this is the last blocker to finally upgrade to 3.4 and then 4.0.
It looks like you are missing the $editForm->isSubmitted() call inside the if condition. I would expect your updated code to look something like this:
$editForm = $this->createForm(new AnamnesisType(), $entity);
$request = $this->getRequest();
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
...
}

Symfony CRUD controller - how does it works?

I'm trying to understand how is Symfoy CRUD controllers works, i've googled a lot and can't find any answers.
So the question is, how does controller knows, which entity is passed to route?
For example:
In this index route, we are calling doctrine manager and then pulling all the comments from database.
/**
* Lists all Comment entities.
*
* #Route("/", name="admin_comment_index")
* #Method("GET")
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$comments = $em->getRepository('AppBundle:Comment')->findAll();
return $this->render('comment/index.html.twig', array(
'comments' => $comments,
));
}
but at next "new" action we are not calling any doctrine instances.Controller seems alredy knows which entity is operating.
/**
* Creates a new Comment entity.
*
* #Route("/new", name="admin_comment_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$comment = new Comment();
$form = $this->createForm('AppBundle\Form\CommentType', $comment);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($comment);
$em->flush();
return $this->redirectToRoute('admin_comment_show', array('id' => $comment->getId()));
}
return $this->render('comment/new.html.twig', array(
'comment' => $comment,
'form' => $form->createView(),
));
}
I guess it's because second route gets "Request" object, is entity stored in it? I'd like to have some deeper explanation.
UPDATE: "new" action seems clear to me now,it was a bad example of what i'm trying to figure, but let's see the "edit" action:
public function editAction(Request $request, Comment $comment)
{
$deleteForm = $this->createDeleteForm($comment);
$editForm = $this->createForm('AppBundle\Form\CommentType', $comment);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($comment);
$em->flush();
return $this->redirectToRoute('admin_comment_edit', array('id' => $comment->getId()));
}
return $this->render('comment/edit.html.twig', array(
'comment' => $comment,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
This time, form is already rendered with data in it, but we are only passing 'id" in request
edit
From where data comes this time? Seems like from comment object,which is passed into controller, but i dont see where it come from.
Sorry for my noobish questions and bad english!
I think you are making it complicated assuming controller knows what to do.
It's actually what code is written inside a controller defines what controller does. For example :
indexAction supposed to get you list of comments from database, hence you need to get EntityManager first to fetch the data. this controller doesn't deal with form as its not required.
newAction supposed to create a new instance of the Comment entity and generate a form to be filled up by user, when submitted Request parameter catches all data and saves to the database. hence, unless you get data from a submitted form, you don't need entity manager to deal with database.
Also, don't assume these are limited to what controller can do, You can customise any controller as per you requirement.
Hope it make sense.
newAction does get an EntityManager instance inside the if statement.
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
//...
And then it uses this manager to persist the object and flush.
When you load the newAction page, it created a new comment object, and sends that to the formbuilder. After that, it makes sure that the form data is placed onto the new Comment object, allowing it to be persisted.
In newAction function:
First, your form is mapped with your entity :
$comment = new Comment();
$form = $this->createForm('AppBundle\Form\CommentType', $comment);
So Symfony knows that you deal with Comment objects in your form
Then when you submit the form, handleRequest() recognizes this and immediately writes the submitted data back into the Comment object
Finally, if object is valid, you just have to save it in the database thanks to entityManager
So between the request and your formType Symfony knows what he wants
I was wondering the same when I created the first controller via generate:doctrine:crud.
As I'm new to symfony as well, what I state is still based on some assumptions. If I'm wrong, I'm thankful for any correction as it helps me learning.
The Kernel seems to recognises what your controller function accepts based on type hinting. By this, it determines whether your controller function accepts the Request object or not.
Further more, the arguments from the route are resolved and parsed with respect to the hinted types. This should
The magic is then done via Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter Class.
By type hinting your desired object Comment, the id parameter is converted to the associated object and loaded from your database.
By the way, what I couldn't figure out so far: is it possible to use a different order of parameters in my route than in my function?
e.g. is it possible to do
/user/{user_id}/{comment_id}
with
public function funWithUserComment( Request $request, Comment $comment, User $user)
{
//..
}
Hope, it helps you though.

Symfony2 REST API - Partial update

I'm building a REST API with FOSRestBundle in Symfony2. I'm using forms to create and update entities with Doctrine2. Everything works fine if I send all form fields. Example:
{"first_name":"Pi","last_name":"Wi"}
The person is inserted fine but now I want to update only the last name.
{"last_name":"Wi"}
The problem is that the first name is empty after the update because the form updates the entity with an "null" value (because it isn't given). Is it possible to just update the last name and ignore the first name?
Sure, it's possible.
First, in terms of RESTful that would be a PATCH request, so if you're using the ClassResourceInterface based controller approach, you'll have to add a patchAction method in your controller.
Then, when processing a submitted form, you'll need to pass a false $clearMissing option to your form's submit method call in the controller, like this:
<?php
// in your controller's patchAction:
/** #var \Symfony\Component\Form\FormInterface $form */
/** #var \Symfony\Component\HttpFoundation\Request $request */
$form->submit($request, false);
This will tell the form component to only update the fields passed from the form, without clearing the missing fields (as the parameter's name says). See the source code for reference.
Note though, that passing a Request to a FormInterface::submit() method will be deprecated as of Symfony 3.0, so this answer is for Symfony 2.x.

Symfony2 Form pre-fill fields with data

Assume for a moment that this form utilizes an imaginary Animal document object class from a ZooCollection that has only two properties ("name" and "color") in symfony2.
I'm looking for a working simple stupid solution, to pre-fill the form fields with the given object auto-magically (eg. for updates ?).
Acme/DemoBundle/Controller/CustomController:
public function updateAnimalAction(Request $request)
{
...
// Create the form and handle the request
$form = $this->createForm(AnimalType(), $animal);
// Set the data again << doesn't work ?
$form->setData($form->getData());
$form->handleRequest($request);
...
}
You should load the animal object, which you want to update. createForm() will use the loaded object for filling up the field in your form.
Assuming you are using annotations to define your routes:
/**
* #Route("/animal/{animal}")
* #Method("PUT")
*/
public function updateAnimalAction(Request $request, Animal $animal) {
$form = $this->createForm(AnimalType(), $animal, array(
'method' => 'PUT', // You have to specify the method, if you are using PUT
// method otherwise handleRequest() can't
// process your request.
));
$form->handleRequest($request);
if ($form->isValid()) {
...
}
...
}
I think its always a good idea to learn from the code generated by Symfony and doctrine console commands (doctrine:generate:crud). You can learn the idea and the way you should handle this type of requests.
Creating your form using the object is the best approach (see #dtengeri's answer). But you could also use $form->setData() with an associative array, and that sounds like what you were asking for. This is helpful when not using an ORM, or if you just need to change a subset of the form's data.
http://api.symfony.com/2.8/Symfony/Component/Form/Form.html#method_setData
The massive gotcha is that any default values in your form builder will not be overridden by setData(). This is counter-intuitive, but it's how Symfony works. Discussion:
https://github.com/symfony/symfony/issues/7141

removing the form name from post field in symfony2

I'm trying to use symfony2 to create a web service. I'd like the webservice to be structured and listen for:
POST to /teams/list with params key1=value and key2=value2
For validation purposes, i've created a TeamForm object and a TeamFormModel to validate the data against (using annotations). The problem i'm having is that the form is looking for team[key1] and team[key2] instead of just key1 and key2 to bind to the TeamFormModel.
Is there a way to configure the form to not use the team[*]?
If you are using the 2.1 branch, it's easy you can simply create a form with an empty name.
$form = $this->get('form.factory')->createNamed(
'', // the name
new TeamType(), // the type
$team // the data
);
$form->bindRequest($request);
So it will work as you are expecting.
But if you are using the 2.0 branch, from what I know, it's not supported and you have to do the binding manually:
$form = $this->createForm(new TeamType(), $team);
$from->bind($request->request->all());
You can validate entity without creating form. You can create entity object from POST data and pass it to validator. See validation section of the cookbook.
If you don't like to create entity object from request parameters every time then you can post data in json or xml format and then de-serialize into entity object using JMSSerializerBundle.

Categories