Read, Update, and Delete


In this video we are going to continue on with the basics of Doctrine - covering:

  • creating a new entity
  • updating an existing entity
  • deleting an existing entity

Combined with the previous video, these four actions cover the core interactions with Doctrine / your database.

As mentioned in the write up to the previous video, in a real world application you will ideally not do the following in your controllers - prefering to use Symfony Services for this kind of logic instead.

However, as beginners, and because we already have plenty of new things to learn, in this video we will simplify our lives by working directly in the controller actions. Just be aware, this is not good practice outside of a learning environment.

As we already covered how to create our database, create a table from an entity, and then list out what we have in our database in the previous two videos, we are now going to cover how to make an entity from inside a Symfony application.

For each of the three actions - create, update, and delete - we are going to need to work with our configured entity / entities. To do this, we need to use the Entity Manager.

The Entity Manager is responsible for the process of persisting objects to, and fetching objects from, the database.

If your Symfony application uses Doctrine, you will frequently use the Entity Manager. Therefore, it is well worth reading the Doctrine documentation page on Working With Objects, where the Entity Manager is covered in greater detail.

Let's review the code, then go over what each line is doing:

// src/AppBundle/Controller/RedditController.php

    /**
     * @Route("/create/{text}", name="create")
     */
    public function createAction($text)
    {
        $em = $this->getDoctrine()->getManager();

        $post = new RedditPost();
        $post->setTitle('hello ' . $text);

        $em->persist($post);
        $em->flush();

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

Firstly, we get hold of the Entity Manager - after all, we are going to need to manage our entities :)

You will frequently see this syntax. $em is a commonly used variable name to represent our entity manager.

Again, as mentioned in the previous video write up, the $this->getDoctrine() method is a convenience method only available to us when we extend from Symfony's abstract controller. This will not always be available to us in every class, just because we are using Symfony. I see this mistake again and again when people are new to the framework.

Next, we create a new instance of our RedditPost entity. Remember, an entity is nothing more than a plain old PHP class with some specified Doctrine metadata (annotations in our case) telling Doctrine how the database table's fields match up to the class properties.

Once we have new'd up the RedditPost, we have access to any accessors (getters / setters / other methods) available on that class. We added in the public functions for getTitle and setTitle when creating our entity, and again, as this is just a plain old PHP class, we can treat it like any other class in our project.

Up to this point, if you have ever worked with PHP classes at all, we have done nothing out of the ordinary. The next step is where things change a little.

We now need to tell Doctrine that our PHP class is to be saved off, according to the metadata specified on the RedditPost entity / class we have created. To do this, we must persist this entity using the entity manager:

$em->persist($post);

But here in lies the first 'gotcha'. A persist is only enough to tell Doctrine that we want to write this change to the database. It will not actually perform that change. To do this, we must flush our pending changes to the database:

$em->flush();

Notice that we do not pass in anything to the flush command. This is another point of confusion I see in beginners, where they try to call flush once per entity:

`$em->flush($someEntity);` // this is wrong! 

Critically, if you do not call flush, your changes for the current request will be lost! Though thankfully, this is something you will likely spot relatively quickly during development :)

Behind the scenes, Doctrine is cleverly grouping and managing our queries to make database interaction more efficient. This, unfortunately, does add complexity to your database interactivity - but for the most part, you need never worry about this. You may wish to read a little more on this topic.

Once we've saved our data (persist and flush), we can use another of Symfony's controller convenience methods to redirect the user to a named route. The route we defined in the previous video, as it happens.

// src/AppBundle/Controller/RedditController.php

    /**
     * @Route("/update/{id}/{text}", name="update")
     */
    public function updateAction($id, $text)
    {
        $em = $this->getDoctrine()->getManager();

        $post = $em->getRepository('AppBundle:RedditPost')->find($id);

        if (!$post) {
            throw $this->createNotFoundException('thats not a record');
        }

        /** @var $post RedditPost */
        $post->setTitle('updated title ' . $text);

        $em->flush();

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

Updating an entity is similar to creating - only, we do not have to call persist.

First, we get access to the entity manager as in the createAction example.

Next, we use the entity manager to ask the RedditPost repository for an entity with a specific ID. We covered this in the previous video, so be sure to watch that if this is new to you.

As we allow site users to specify the ID, they may very well request some non-existant ID. If we don't explicitly handle this situation, Symfony will throw an exception for us. The exception helps developers, but may not be what you want to happen in production.

Here, we are throwing a rather silly exception message using, again, a Symfony controller convenience method - createNotFoundException.

Assuming we haven't thrown an exception, we can now start working with the retrieved entity. In this case, we simply set the title to some text passed in from the URL. Hey... it's just an example :)

Because our RedditPost ($post) entity is in a 'managed' state by Doctrine, calling $em->flush(); will inspect our $post object, see that the title property has changed, and then on $em->flush(); will run a SQL Update command for us. Again, if you forget to include the call to flush then nothing will happen.

Then, lastly we do the redirect to the list route as before.

// src/AppBundle/Controller/RedditController.php

    /**
     * @Route("/delete/{id}", name="delete")
     */
    public function deleteAction($id)
    {
        $em = $this->getDoctrine()->getManager();

        $post = $em->getRepository('AppBundle:RedditPost')->find($id);

        if (!$post) {
            return $this->redirectToRoute('list');
        }

        $em->remove($post);
        $em->flush();

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

The process for deleting an entity is extremely similar to the two we have already covered.

First we get the entity manager, then use it to find a RedditPost entity by a passed in ID. If there isn't an entity with that ID, then redirect to the named list route.

Where this differs in the in the call to $em->remove($post);.

Much like in a persist, we have to explicitly tell Doctrine that we want to remove an entity. This will queue the entity up for deletion (by executing a SQL Delete statement), but will not actually delete the record / execute the delete statement in the database, until we call flush explicitly.

This can lead to peculiar situations where an entity may still be returned from a query result, even after the entity has been remove'd, but not yet flush'ed. This is an uncommon occurence, but it is possible.

And then finally we redirect as we have done in every other action.

But What About...

In this, and the previous video, we have covered the basics - Create, Read, Update, Delete - CRUD.

We haven't yet covered relationships - where one record knows about or depends on one or more other records (one to many, many to many, etc).

These relationships do have an impact on the above. They also make querying a little more involved, should we need a more specific subset of our data.

These topics will be covered in the next few videos.

Code For This Course

Get the code for this course.

Episodes