Symfony 3 Blog Demo Remix (Part 3): Creating a Category entity

Published Jan 06, 2017Last updated Jan 18, 2017
Symfony 3 Blog Demo Remix (Part 3): Creating a Category entity

Creating a Category entity

For this part of this tutorial, we are going to be extending the application by creating a Category entity which will get tied to each Post entity (via an M:1 relationship). You may have to read the first and second parts of these tutorial series to complete your application.

But for this third part, at first glance, this task doesn't seem like it's going to be too tough to create (which really isn't). However, before making too many assumptions, we should create a quick list of what our entity needs to do and where it needs to go.

Designing the Category entity

My site is called Code As A Last Resort... That being said, let's save the coding part until the very end, when we have no further assumptions to make or possible caveats to overcome. Here is a bare-bones list of what this "category" extension is going to need:

  • Create a Category class with the namespace AppBundle\Entity to define which properties our new entity should have.
  • The Category class should have some basic fields as well as getters and setters to access each field:
  1. Category Name
  2. Category Status
  3. A Many-to-One relationship to the Post entity
  4. An ID field to properly define the entity and establish a primary key
  5. A date that category was created
  6. A slug for using in the URL for SEO purposes
  7. An isDeleted flag to indicate if a category is considered deleted in the system
  • Create a form for creating new categories in the backend
  • Create a controller that will handle the category entities displayed on the front & backend
  • Modify the Post entity to include a "category" field which will have a one-to-many relationship with the post
  • Auto update our database to reflect our new entity
  • To keep a clear separation of concerns, create a new repository for the category entity to define any custom queries or operations it may have

Damnit! That's actually quite a bit of work when you break it down. No worries though its not as hard as it may seem. Let's get started.

A category obviously needs a name (like "stories", "news", "updates", etc). In addition, each category should include a status field that will be used to track if it's been deleted and/or if the category is active and should be displayed on the page. This is more desirable than just deleting it completely from the database because you still have a record of what it was. Finally, the category entity should have a relationship with the Post entity to properly be able to track which posts belong to which category. We will configure this relationship directly in the Category and Post entities (under AppBundle\Entity namespace) via annotations.

The Category entity

src/AppBundle/Entity/Category.php</

/*
 * This file is part of Symfony3 Blog Demo REMIX package.
 *
 * (c) Jesse Griffin <codeasalastresort@gmail.com>
 *
 * The Category Entity
 */

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\CategoryRepository")
 * @ORM\Table(name="category")
 *
 * Defines the properties of the Category entity to represent the blog categories.
 *
 * @author Jesse Griffin <hemham914@gmail.com>
 */
class Category
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * @ORM\Column(type="string",nullable=true)
     */
    private $slug;

    /**
     * @ORM\Column(type="datetime")
     * @Assert\DateTime()
     */
    private $createdAt;

    /**
     * @ORM\Column(type="boolean")
     */
    private $isDeleted = false;

    public function __construct()
    {
        $this->createdAt = new \DateTime();
        $this->isDeleted = false;
    }

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param mixed $name
     * @return Category
     */
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * @param mixed $createdAt
     * @return Category
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;
        return $this;
    }

    public function getSlug()
    {
        return $this->slug;
    }

    public function setSlug($slug)
    {
        $this->slug = $slug;
        return $this;
    }

    public function isDeleted()
    {
        return $this->isDeleted;
    }

    public function delete()
    {
        $this->isDeleted = true;
        return $this;
    }

}

This entity is pretty self-explanatory. It has some basic fields that, together, comprise the properties that will be used to create categories for our posts. The only notable thing here is the doctrine annotations, which we used to describe each field from a database perspective.

Creating a Repository for the Category entity

If you take a look inside the src/AppBundle/Repository directory, you will see that there are actually two repositories that come stock with the Symfony 3 Blog Demo. Examine the PostRepository.php and the UserRepository.php files briefly to get a better idea of what we need to build for our Category entity. If you are completely clueless as to what a repository is, this tutorial will help.

Basically what a repository does is act as a container that holds any custom queries specific to a particular entity. There are multiple reasons as to why you would want to build your database objects in this manner:

  • It keeps lengthy and complex queries isolated from the controller code (separation of concerns).
  • It makes it possible to unit test the queries in isolation and makes automation easier when building a testing implementation for your application.
  • Code reuse is possible when using repositories to hide all the complex queries an entity might need.

Thanks to Doctrine (and Symfony of course), a repository is as simple to build as writing an extra line of code in the entity annotation definition and running one command. For our Category entity, you will notice that the annotation defining the class as an entity contains a repository annotationthat looks like this:

@ORM\Entity(repositoryClass="AppBundle\Repository\CategoryRepository")

This is what tells the doctrine that there is a repository class that is associated with that particular entity class (although it doesn't exist quite yet). What's really neat about this mechanism is that you can generate the repository from scratch with just a single Doctrine command (the repository class name must be specified in the @ORM\Entity annotation as it is above):

  php bin/console doctrine:generate:entities AppBundle

When we run this command, the entity's repository class is created in the AppBundle\Repository namespace and is named...you guessed it, CategoryRepository.php. Open up this file and you should see something similar to the other repository classes under the AppBundle\Repository namespace.

One thing to note about this command is that you can also run it to automatically generate any missing getters and setters from your entity class. Pretty handy indeed!

Inserting custom query logic into the Category repository

At this point, any custom query logic that the particular entity needs can be added in the form of a new class method. For instance, for our Category entity, we are going to want, at some point, to pull in all categories in the database that are not marked as "deleted" (i.e. have an active status) and sorted in an ascending order by their respective "name" field. To accomplish this, we would create a new method named findAllOrderedByNameAsc() and it might look something like this:

  public function findAllOrderedByNameAsc()
  {
    return $this->getEntityManager()
          ->createQuery(
            'SELECT c FROM AppBundle::Category c ORDER BY c.name ASC'
          )
          ->getResult();
  }

Something to note here is that the Doctrine entity manager can be accessed anywhere within the repository class via a call to `$this->getEntityManager()```.

From there, you can query the database directly, retrieve a specific repository from the entity manager, create transactions and a bunch of other useful things as well. For more info on the entity manager and its capabilities, check out the documentation here.

Now that we have our Category entity thought out and coded, we need to tell Doctrine of its existence so to have it build the related database tables that we need in order for this to function.

We will get to that on the next section of the tutorial. Keep checking back!

Complete series:


This tutorial is originally posted by the author on his blog. This version has been edited for clarity.

Discover and read more posts from Jesse Griffin
get started