Symfony2 Form Entity Update - forms

Can anyone please show me a specific example of a Symfony2 form entity update? The book only shows how to create a new entity. I need an example of how to update an existing entity where I initially pass the id of the entity on the query string.
I'm having trouble understanding how to access the form again in the code that checks for a post without re-creating the form.
And if I do recreate the form, it means I have to also query for the entity again, which doesn't seem to make much sense.
Here is what I currently have but it doesn't work because it overwrites the entity when the form gets posted.
public function updateAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$testimonial = $em->getRepository('MyBundle:Testimonial')->find($id);
$form = $this->createForm(new TestimonialType(), $testimonial);
$request = $this->get('request');
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
echo $testimonial->getName();
if ($form->isValid()) {
// perform some action, such as save the object to the database
//$testimonial = $form->getData();
echo 'testimonial: ';
echo var_dump($testimonial);
$em->persist($testimonial);
$em->flush();
return $this->redirect($this->generateUrl('MyBundle_list_testimonials'));
}
}
return $this->render('MyBundle:Testimonial:update.html.twig', array(
'form' => $form->createView()
));
}

Working now. Had to tweak a few things:
public function updateAction($id)
{
$request = $this->get('request');
if (is_null($id)) {
$postData = $request->get('testimonial');
$id = $postData['id'];
}
$em = $this->getDoctrine()->getEntityManager();
$testimonial = $em->getRepository('MyBundle:Testimonial')->find($id);
$form = $this->createForm(new TestimonialType(), $testimonial);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// perform some action, such as save the object to the database
$em->flush();
return $this->redirect($this->generateUrl('MyBundle_list_testimonials'));
}
}
return $this->render('MyBundle:Testimonial:update.html.twig', array(
'form' => $form->createView()
));
}

This is actually a native function of Symfony 2 :
You can generate automatically a CRUD controller from the command line (via doctrine:generate:crud) and the reuse the generated code.
Documentation here :
http://symfony.com/doc/current/bundles/SensioGeneratorBundle/commands/generate_doctrine_crud.html

A quick look at the auto-generated CRUD code by the Symfony's command generate:doctrine:crudshows the following source code for the edit action
/**
* Displays a form to edit an existing product entity.
*
* #Route("/{id}/edit", name="product_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Product $product)
{
$editForm = $this->createForm('AppBundle\Form\ProductType', $product);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('product_edit', array('id' => $product->getId()));
}
return $this->render('product/edit.html.twig', array(
'product' => $product,
'edit_form' => $editForm->createView(),
));
}
Note that a Doctrine entity is passed to the action instead of an id (string or integer). This will make an implicit parameter conversion and saves you from manually fetching the corresponding entity with the given id.
It is mentioned as best practice in the Symfony's documentation

Related

How to send flash message to form error field (Symfony)

This is my registration form code:
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$user = new User();
$form = $this->createForm(RegisterType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
$user->setPlan('1');
$datetime = new \DateTime();
$datetime->modify('+30 day');
$user->setExpiration($datetime);
$user->setActive('0');
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash('success', 'Your account has been created. Check your inbox for verification e-mail.');
}
return $this->render('home/register.html.twig', array(
'form' => $form->createView(),
));
}
At the end of saving user into database there's set Flash message. Is there any way of showing that message through form error field?
No you cannot add a flash message to a form's errors. The only way to manipulate the errors of a form through it's class API is the public function addError(FormError $error) but as you can see it's only accepts arguments of type FormError not strings.

Symfony3 : multi-steps form and flush after forward

I have a form to add a new prescriber in my database. The first step consists in informing the various informations about the prescriber.
Then, I check if there are similar prescribers before adding it (2nd step with a 2nd form) and if there are, I ask the user to confirm.
In short, I have a 1-step form or a 2-steps form, depending on duplicates.
I tried with CraueFormFlowBundle but I don't know how to implement my conditional second step. My tests were inconclusive. So I decided to use forward method in my controller, and I like it !
But, I can't manage to flush my prescriber at the end of the 2nd step (after forwarding), I have this error : Unable to guess how to get a Doctrine instance from the request information for parameter "prescriber".
addAction (= step 1)
/**
* Add a new prescriber
*
* #Route("/prescribers/add", name="prescriber_add")
*/
public function addAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$rp = $em->getRepository('AppBundle:Prescriber');
$p = new Prescriber();
// build the form
$form = $this->createForm(AddPrescriberType::class, $p);
// handle the submit
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
# search if a prescriber already exists
$qb = $rp->createQueryBuilder('p');
$qb->where($qb->expr()->eq('p.rpps', ':rpps'))
->orWhere($qb->expr()->andX(
$qb->expr()->like('p.lastname', ':name'),
$qb->expr()->like('p.firstname', ':firstname')
))
->setParameter('rpps', $p->getRpps())
->setParameter('name', '%'.$p->getLastname().'%')
->setParameter('firstname', '%'.$p->getFirstname().'%');
$duplicates = $qb->getQuery()->getArrayResult();
# there are duplicates
if (!empty($duplicates)) {
$em->persist($p);
// confirm the addition of the new prescriber
$params = array('prescriber' => $p, 'duplicates' => $duplicates);
$query = $request->query->all();
return $this->forward('AppBundle:Prescriber:addConfirm', $params, $query);
} else {
$em->persist($p); # save the prescriber
$em->flush(); # update database
$this->addFlash('p_success', 'The prescriber has been created successfully');
return $this->redirectToRoute('prescriber');
}
}
// show form
return $this->render('prescribers/form-step1.html.twig', array(
'form' => $form->createView()
));
}
addConfirmAction (= step 2)
/**
* Confirm addition of a new prescriber
*
* #Route("/prescribers/add/confirm", name="prescriber_add_confirm")
*/
public function addConfirmAction(Prescriber $prescriber, $duplicates, Request $request) {
$em = $this->getDoctrine()->getManager();
$form = $this->createFormBuilder()->getForm();
if ($form->handleRequest($request)->isValid()) {
$em->persist($prescriber);
$em->flush();
$this->addFlash('p_success', 'Prescriber has been created successfully');
return $this->redirectToRoute('prescriber');
}
// show confirm page
return $this->render('prescribers/form-step2.html.twig', array(
'h1_title' => 'Ajouter un nouveau prescripteur',
'form' => $form->createView(),
'p' => $prescriber,
'duplicates'=> $duplicates
));
}
I think the problem comes from the fact that I have 2 forms submissions...
I found a solution by using the session.
(I know it's not a perfect way but I didn't find other one)
For Symfony 3.3.*
use Symfony\Component\HttpFoundation\Session\SessionInterface;
public function addAction(Request $request, SessionInterface $session) {
// [...]
# there are duplicates
if (!empty($duplicates)) {
$data = $form->getData();
$session->set('prescriber', $data);
$session->set('duplicates', $duplicates);
return $this->forward('AppBundle:Prescriber:addConfirm');
// [...]
}
public function addConfirmAction(Request $request, SessionInterface $session) {
$em = $this->getDoctrine()->getManager();
$p = $session->get('prescriber');
$duplicates = $session->get('duplicates');
// empty form with only a CSRF field
$form = $this->createFormBuilder()->getForm();
if ($form->handleRequest($request)->isValid()) {
$em->persist($p);
$em->flush();
$this->addFlash('p_success', 'The prescriber has been created successfully');
return $this->redirectToRoute('prescriber');
}
// show confirm page
return $this->render('prescribers/form-step2.html.twig', array(
'form' => $form->createView(),
'prescriber'=> $p,
'duplicates'=> $duplicates
));
}

Symfony2 FOSRestBundle form submission on PUT action with Request Body Listener

Referencing this question: JMS Serializer DateTime not deserializing?, I have narrowed this down to the folowing:
I have got a controller with a put action using a Request Body Listener:
/**
* #ParamConverter("client", converter="fos_rest.request_body")
*/
public function putClientAction($id, Client $client)
{
$logger = $this->get('logger');
$logger->info(serialize((array) $client));
$logger->info("ID: " . $id);
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('SomeBundle:Client')->find($id);
$logger->info(serialize($entity));
$form = $this->get('form.factory')->createNamed('', new \SomeBundle\Form\ClientType(), $entity);
$form->submit((array) $client, false);
$logger->info(serialize($entity));
$em->persist($entity);
$em->flush();
return $this->get('fos_rest.view_handler')->handle($this->view(null, Codes::HTTP_OK));
}
My question concerns the $form->submit() call, or rather, how would I go about and actually submit the incoming $client entity? I have tried submitting the object, or an (array) representation of it, with the $clearmissing flag set to false to avoid null values being passed in, but an UPDATE never happens.
To be sure, the logged entity representations look fine, it's just that $entity never gets filled with $clients values.
Got any hints on what I'm doing wrong?
Edit
This is what a PUT request looks like:
{
"shortname":"...",
"officialname":"...",
"shortinfo":"..."
...
}
Edit 2
I've narrowed it down (again) to a problem with the DateTime data type:
...
$logger->info("REQUEST" . print_r($request->request->all(), true));
$form = $this->get('form.factory')->createNamed('', new Type(), $entity);
$form->submit($request, false);
$logger->info($form->getData()->getUpdatedAt()->format('Y-m-d\TH:i:sP'));
The $request object contains an updated DateTime, but it is never passed to the form (the second log shows the original date). The form Type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('updatedAt', 'datetime', array('format' => 'Y-m-d\TH:i:sP'))
}
I guess this is a problem with Symfony Forms and DateTime objects, then?
The problem is how you submit your data. You should use less complex way.
Here is example:
public function putPostAction(Post $post, Request $request)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm('content_post',$post);
$form->submit($request);
if($form->isValid()){
$em->persist($form->getData());
$em->flush();
return $post;
}
return $form;
}
You don't need to query client in your code because controller will automatically convert it for you.
Also you should use Request object, it will contain all data that you've submitted.
$form->submit($request) has a key part here.
$form will return all your validations errors.
My FOSRestConfig
fos_rest:
param_fetcher_listener: true
routing_loader:
default_format: json
view:
view_response_listener: force

FOSRestBundle update only one field at a time using PUT

While ago I have asked same question Symfony PUT does not contain all entity properties mapped and I've got some #hack solution to solve the problem but when the form is more complex, contains choices (arrays) or mapped entities (objects) the solution is not applicable anymore. So I came up with very dirty ideas to make it work such as
if(is_object($event->getForm()->getData())) $event->setData($event->getForm()->getData()->getId());
if(is_array($event->getData()) && empty($event->getData()))
{
$event->setData([$event->getForm()->getData()]);
}
if($event->getForm()->getData() instanceof \DateTime) $event->setData($event->getForm()->getData()->format('Y-m-d H:i:s'));
if(!is_array($event->getData()) && (is_string($event->getForm()->getData()) || is_integer($event->getForm()->getData())))
{
$event->setData($event->getForm()->getData());
}
but it's not even working perfect. So I must ask one more time if there's a better solution to updatejust one value at the time of sending json response, because if I send {"user":{"firstName":"John"}} all other fields belonging to the User form are empty and I cannot send entire resource. I cannot find any solution to this problem.
And here's the Controller
/**
* This endpoint updates an existing Client entity.
*
* #ApiDoc(
* resource=true,
* description="Updates an existing Client entity",
* )
* #ParamConverter("user", class="Software:User", options={"mapping": {"user": "guid"}})
*/
public function putAction(Request $request, $user)
{
$form = $this->createForm(new UserType(['userType' => 'client', 'manager' => $this->getDoctrine()->getManager()]), $user, ['method' => 'PUT']);
$form->handleRequest($request);
if($form->isValid())
{
$manager = $this->getDoctrine()->getManager();
$manager->flush();
return $this->view([
'user' => $user
]);
}
return $this->view($form);
}
I am going to answer my own question.
The answer is to use PATCH method instead of PUT.
Here's the modified code:
/**
* This endpoint updates an existing Client entity.
*
* #ApiDoc(
* resource=true,
* description="Updates an existing Client entity",
* )
* #ParamConverter("user", class="Software:User", options={"mapping": {"user": "guid"}})
*/
public function patchAction(Request $request, $user)
{
$form = $this->createForm(new UserType(['userType' => 'client', 'manager' => $this->getDoctrine()->getManager()]), $user, ['method' => 'PATCH']);
$form->handleRequest($request);
if($form->isValid())
{
$manager = $this->getDoctrine()->getManager();
$manager->flush();
return $this->view([
'user' => $user
]);
}
return $this->view($form);
}
Please find references:
Symfony2 REST API - Partial update
http://williamdurand.fr/2012/08/02/rest-apis-with-symfony2-the-right-way/
http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
Read carefully PATCH vs. PUT chapters.

How change return render direction in Symfony2

I Have a problem rendering a form in symfony.
I use a comercial template (SmartAdmin) and this template use AJAX to capture all the urls loaded, keep the menus and configuration when the url provide '#'
Normally i can work ok, but in this controller i have a problem when the validation fails
public function createAction(Request $request)
{
$entity = new Clientes();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
$url=$this->generateUrl('ec_main_cliente', array('idcliente' => $entity->getId()));
$url = str_replace('cliente', '#cliente', $url);
return $this->redirect($url);
}
return $this->render('ECMainBundle:Clientes:nuevo_cliente.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
When Form is Invalid , load "ECMainBundle:Clientes:nuevo_cliente.html.twig" without the "#" and lost all the menus and configurations.
Any suggestion please?
Thanks in Advance and sorry for my english
Image work Ok:
Image of the problem:
I still don't have the feeling to fully understand what you're trying to achieve, but I think that you want the redirect always take place. In this case you should restructure your code a bit:
$id = 0;
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
$id = $entity->getId();
}
$url = $this->generateUrl('ec_main_cliente', array('idcliente' => $id));
$url = str_replace('cliente', '#cliente', $url);
return $this->redirect($url);
Then, of course, the last part with the template rendering would be never exectuted.