Updating Existing Doctrine Entities


In this video you will see just how easy it is to re-populate a Symfony form using data retrieved from your database. We will use Doctrine to find a entity matching the passed in ID, and then map this data back on to the form, allowing our website users to edit / update their existing data.

There's a really important and fundamental concept taught in this tutorial that, once learned and understood, will dramatically simplify your experience using Symfony's forms.

This is that forms are a visual way of representing the current entity state.

That sounds like techno-babble, and it largely remains that way until you see the process in action - a perfect use of video for the purposes of training / learning Symfony, in my opinion.

As we saw in the previous video, when you create a form for adding data you can either rely on using the data_class inside your form type's configureOptions method:

// src/AppBundle/Form/Type/YourFormType.php

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\YourEntityHere'
        ]);
    }

And / or, you can explicitly pass in your entity type when creating the form:

// src/AppBundle/Controller/YourEntityController.php

namespace AppBundle\Controller;

use AppBundle\Entity\YourEntityHere;
use AppBundle\Form\Type\YourEntityFormType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class FormExampleController extends Controller
{
    /**
     * @Route("/", name="form_example")
     */
    public function formExampleAction(Request $request)
    {
        $form = $this->createForm(YourEntityFormType::class, new YourEntityHere());

In the previous instance, this was fine because we are only adding new data. At the time that the form is first instantiated, there is no data - we are creating new data.

We can then check if the form has been submitted, and only if so do we need to concern ourselves with saving off the new data. Symfony's form will handle the mapping between the submitted data (via the $request) and the underlying entity.

However, when we have existing data, we must tell the form about this data when the form is initially loaded.

As mentioned in the write up to the previous video, this is why I would advise you always explicitly pass in your entity when creating a form (whatever the forms purpose - add / edit), AND declare the data_type. It just makes your life easier - one less thing that differs between actions. Sure, it's a little more verbose, but it frees up brain space.

Before we go off to the database and grab an entity to re-create our form using that existing entity data, it is altogether easier to demonstrate that there are two distinct steps in this process:

  • fetching data
  • repopulating the form with that data

Having spoken with many developers who are initially learning Symfony, I can tell you that a good deal of confusion comes from mentally associating these two steps together. Splitting them apart makes this whole process easier to understand.

With that in mind, let's completely ignore Doctrine for the moment. Instead, let's re-populate the form using made up data:

// src/AppBundle/Controller/FormExampleController.php

    public function formExampleAction(Request $request)
    {
        $product = new Product();
        $product->setTitle('a brilliant new product');
        $product->setDescription('some interesting description');

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

        return $this->render(':form-example:index.html.twig', [
            'productForm' => $form->createView()
        ]);
    }

Amazingly, we load the form and ... it has our data pre-populating the appropriate title and description fields.

We didn't need Doctrine at all. We create an entity, set up some dummy data on it, and passed it into the createForm method as the second parameter. Symfony's form component then maps our entity data onto the appropriate fields, and renders out a HTML form for us with almost no effort on our part.

Also - the form doesn't care where the data comes from. We made it up. But we could just as easily get that entity from Doctrine, or from some JSON feed, or literally anywhere else that can provide data.

Knowing this, we can replace our fake Product with a real, existing entity we created in the previous video:

    /**
     * @Route("/edit/{productId}", name="form_edit_example")
     */
    public function formEditExampleAction(Request $request, $productId)
    {
        $em = $this->getDoctrine()->getManager();
        $product = $em->getRepository('AppBundle:Product')->find($productId);

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

        return $this->render(':form-example:index.html.twig', [
            'form' => $form->createView()
        ]);
    }

Assuming you pass in a valid ID then you should find your form loads with the data from the entity already populating your form fields. Fantastic.

But wait, there's more!

    /**
     * @Route("/edit/{product}", name="form_edit_example")
     */
    public function formEditExampleAction(Request $request, $productId)
    {
        $em = $this->getDoctrine()->getManager();
        $product = $em->getRepository('AppBundle:Product')->find($productId);

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

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {

            $em->flush();

            return $this->redirectToRoute('form_add_example');
        }

        return $this->render(':form-example:index.html.twig', [
            'form' => $form->createView()
        ]);
    }

When submitting edited data we don't need to do very much. We can simply flush() the changes and find our entity updates properly. Again, a big tick in the box for simplicity.

If you don't understand this, be sure to watch this video which covers Read, Update, and Delete in Doctrine.

This code can be simplified even further:

    /**
     * @Route("/edit/{product}", name="form_edit_example")
     */
    public function formEditExampleAction(Request $request, Product $product)
    {
        $form = $this->createForm(ProductType::class, $product);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {

            $em = $this->getDoctrine()->getManager();
            $em->flush();

            return $this->redirectToRoute('form_add_example');
        }

        return $this->render(':form-example:index.html.twig', [
            'form' => $form->createView()
        ]);
    }

This code is functionally identical to the previous formEditExampleAction code, but makes use of injecting a pre-populated Product entity for us, by asking Symfony to convert the passed in $productId - renamed to $product to make a little more sense - to a Doctrine entity for us before calling this Controller action.

Therefore, as soon as the controller action is called, we no longer need to explicitly call:

$em->getRepository('AppBundle:Product')->find($productId);

And instead, can start using a variable called $product, as though we had made that call.

As we won't need the entity manager to be available unless the form is submitted, we can move the creation of the $em variable inside the if statement. It's a small optimisation, but why not?

And that's it. With very little extra effort we have a fully working 'edit' controller action.

The key thing to understand is that so long as we have an entity - no matter how complex that entity - we can map it directly on to a form that represents it. Once you understand this concept, the form becomes easier to reason about. And understanding this core concept is key to understanding the more complicated form types that Symfony will allow us to create.

Code For This Course

Get the code for this course.

Episodes