A free and open-source book on Zend Framework for beginners


12.6. Creating Entities

For the Application module, entities are (by convention) stored inside the Entity directory under the module's source directory. Entity classes live inside the Application\Entity namespace.

12.6.1. Adding Post Entity

We start with creating the Post entity. Create the Post.php file under module's Entity directory. (If you haven't created the Entity directory yet, its the right time to do that.) Put the following code into the file:

<?php
namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * This class represents a single post in a blog.
 * @ORM\Entity
 * @ORM\Table(name="post")
 */
class Post 
{
  // Post status constants.
  const STATUS_DRAFT       = 1; // Draft.
  const STATUS_PUBLISHED   = 2; // Published.

  /**
   * @ORM\Id
   * @ORM\GeneratedValue
   * @ORM\Column(name="id")   
   */
  protected $id;

  /** 
   * @ORM\Column(name="title")  
   */
  protected $title;

  /** 
   * @ORM\Column(name="content")  
   */
  protected $content;

  /** 
   * @ORM\Column(name="status")  
   */
  protected $status;

  /**
   * @ORM\Column(name="date_created")  
   */
  protected $dateCreated;
  
  // Returns ID of this post.
  public function getId() 
  {
    return $this->id;
  }

  // Sets ID of this post.
  public function setId($id) 
  {
    $this->id = $id;
  }

  // Returns title.
  public function getTitle() 
  {
    return $this->title;
  }

  // Sets title.
  public function setTitle($title) 
  {
    $this->title = $title;
  }

  // Returns status.
  public function getStatus() 
  {
    return $this->status;
  }

  // Sets status.
  public function setStatus($status) 
  {
    $this->status = $status;
  }
    
  // Returns post content.
  public function getContent() 
  {
    return $this->content; 
  }
    
  // Sets post content.
  public function setContent($content) 
  {
    $this->content = $content;
  }
    
  // Returns the date when this post was created.
  public function getDateCreated() 
  {
    return $this->dateCreated;
  }
    
  // Sets the date when this post was created.
  public function setDateCreated($dateCreated) 
  {
    $this->dateCreated = $dateCreated;
  }
}

In the code above, we have the following things:

Please note that for properties we (by convention) use camel-case names (like $dateCreated), while for database columns we use "canonicalized" names (in lower-case and with underscores separating words in a name, like date_created).

Table 12.2. Properties of the Post entity
Property Mapped on Column Description
$id id Unique ID of this post.
$title title Title of this post.
$content content Content of this post.
$status status Status (draft/published) of this post.
$dateCreated date_created Date when this post was created.
Table 12.3. Getter and setter methods of the Post entity
Method Description
getId() Returns ID of this post.
setId($id) Sets ID of this post.
getTitle() Returns title.
setTitle($title) Sets title.
getStatus() Returns status (draft/published).
setStatus($status) Sets status.
getContent() Returns post content.
setContent($content) Sets post content.
getDateCreated() Returns the date when this post was created.
setDateCreated() Sets the date when this post was created.

Note that we do not mark entity class methods with Doctrine annotations. There is just no need to do that. However, you may mark methods with usual comments and non-Doctrine Docblock annotations, if you strongly wish.

12.6.2. Adding the Comment and Tag Entities

By analogy with the Post entity, we next create the Comment and the Tag entity classes in the Entity directory. To do that, first, create Comment.php file and put the following code inside of it:

<?php
namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * This class represents a comment related to a blog post.
 * @ORM\Entity
 * @ORM\Table(name="comment")
 */
class Comment 
{
  /**
   * @ORM\Id
   * @ORM\Column(name="id")
   * @ORM\GeneratedValue
   */
  protected $id;

  /** 
   * @ORM\Column(name="content")  
   */
  protected $content;

  /** 
   * @ORM\Column(name="author")  
   */
  protected $author;
    
  /** 
   * @ORM\Column(name="date_created")  
   */
  protected $dateCreated;

  // Returns ID of this comment.
  public function getId() 
  {
    return $this->id;
  }

  // Sets ID of this comment.
  public function setId($id) 
  {
    $this->id = $id;
  }
    
  // Returns comment text.
  public function getContent() 
  {
    return $this->content;
  }

  // Sets status.
  public function setContent($content) 
  {
    $this->content = $content;
  }
    
  // Returns author's name.
  public function getAuthor() 
  {
    return $this->author;
  }

  // Sets author's name.
  public function setAuthor($author) 
  {
    $this->author = $author;
  }

  // Returns the date when this comment was created.
  public function getDateCreated() 
  {
    return $this->dateCreated;
  }
    
  // Sets the date when this comment was created.
  public function setDateCreated($dateCreated) 
  {
    $this->dateCreated = $dateCreated;
  }
}

Next, create Tag.php file and put the following code inside of it:

<?php
namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * This class represents a tag.
 * @ORM\Entity
 * @ORM\Table(name="tag")
 */
class Tag 
{
  /**
   * @ORM\Id
   * @ORM\GeneratedValue
   * @ORM\Column(name="id")
   */
  protected $id;

  /** 
   * @ORM\Column(name="name") 
   */
  protected $name;

  // Returns ID of this tag.
  public function getId() 
  {
    return $this->id;
  }

  // Sets ID of this tag.
  public function setId($id) 
  {
    $this->id = $id;
  }

  // Returns name.
  public function getName() 
  {
    return $this->name;
  }

  // Sets name.
  public function setName($name) 
  {
    $this->name = $name;
  }
}

Since the Comment and Tag entities are analogous to the Post entity, we don't provide detailed description of the code above.

Please note that we do not create an entity for the fourth auxiliary table post_tag. That table will be indirectly used further in this chapter when defining relationships between entities.

12.6.3. Specifying Relationships between Entities

Now it is time to use annotations to define relationships between entities. If you remember, we have two relationships between our entities:

In Doctrine, to express a relationship between two entities, you add a private property paired with Docblock annotation.

For detailed information about relationships between entities in Doctrine, please read this page of Doctrine documentation.

12.6.3.1. OneToMany/ManyToOne

First, let's define one-to-many relationship between the Post and Comment entities. Modify the Post.php file and add the following lines:

<?php
// ...
use Doctrine\Common\Collections\ArrayCollection;
use Application\Entity\Comment;

/**
 * This class represents a single post in a blog.
 * @ORM\Entity
 * @ORM\Table(name="post")
 */
class Post 
{
  // ...  
  
  /**
   * @ORM\OneToMany(targetEntity="\Application\Entity\Comment", mappedBy="post")
   * @ORM\JoinColumn(name="id", referencedColumnName="post_id")
   */
  protected $comments;
    
  /**
   * Constructor.
   */
  public function __construct() 
  {
    $this->comments = new ArrayCollection();               
  }
    
  /**
   * Returns comments for this post.
   * @return array
   */
  public function getComments() 
  {
    return $this->comments;
  }
    
  /**
   * Adds a new comment to this post.
   * @param $comment
   */
  public function addComment($comment) 
  {
    $this->comments[] = $comment;
  }
}

As you can see from the code above, we added the $comments property (line 19). This property will be the collection of comments related to certain post.

We initialize the $comments property in class constructor (lines 24-27). By assigning it with a new instance of Doctrine\Common\Collections\ArrayCollection class.

A Doctrine ArrayCollection is an array of objects, like usual PHP array, but with additional features required by Doctrine. It is implemented in Doctrine\Common component.

In lines 15-18, we add Doctrine annotations to the $comments property, so Doctrine knows how to get all comments associated with the post:

The getComments() method (lines 33-36) allows getting all comments associated with the post.

We also added the addComment() method (lines 42-45) for adding new comment to post. You can notice that we use the [] operator, just like we do with a typical PHP array.

Vice versa, we define the other side of this relationship by modifying the Comment entity as follows:

<?php
// ...
use Doctrine\Common\Collections\ArrayCollection;

// ...
class Comment 
{
  /**
   * @ORM\ManyToOne(targetEntity="\Application\Entity\Post", inversedBy="comments")
   * @ORM\JoinColumn(name="post_id", referencedColumnName="id")
   */
  protected $post;
     
  /*
   * Returns associated post.
   * @return \Application\Entity\Post
   */
  public function getPost() 
  {
    return $this->post;
  }
    
  /**
   * Sets associated post.
   * @param \Application\Entity\Post $post
   */
  public function setPost($post) 
  {
    $this->post = $post;
    $post->addComment($this);
  }
}

In the code above, we added the $post private property to the entity class. This is not a collection, but a single instance of Post class, because single comment always belongs to single post. The annotation tags @ORM\ManyToOne and @ORM\JoinColumn are analogous to those we covered before.

12.6.3.2. ManyToMany

Let's now express the many-to-many relationship between the Post and Tag entities. For this relationship, we indirectly use the auxiliary post_tag table.

Modify the Post entity as follows:

<?php
//...
use Application\Entity\Tag;

//...
class Post 
{
  //...
    
  /**
   * @ORM\ManyToMany(targetEntity="\Application\Entity\Tag", inversedBy="posts")
   * @ORM\JoinTable(name="post_tag",
   *      joinColumns={@ORM\JoinColumn(name="post_id", referencedColumnName="id")},
   *      inverseJoinColumns={@ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
   *      )
   */
  protected $tags;
    
  // Constructor.
  public function __construct() 
  { 
    //...  
    $this->tags = new ArrayCollection();        
  }

  // Returns tags for this post.
  public function getTags() 
  {
    return $this->tags;
  }      
    
  // Adds a new tag to this post.
  public function addTag($tag) 
  {
    $this->tags[] = $tag;        
  }
    
  // Removes association between this post and the given tag.
  public function removeTagAssociation($tag) 
  {
    $this->tags->removeElement($tag);
  }
}

In the code above, we do the following:

Finally, modify the Tag entity as follows:

<?php
//...
use Doctrine\Common\Collections\ArrayCollection;

class Tag 
{
  // ...
  
  /**
   * @ORM\ManyToMany(targetEntity="\Application\Entity\Post", mappedBy="tags")
   */
  protected $posts;
    
  // Constructor.
  public function __construct() 
  {        
    $this->posts = new ArrayCollection();        
  }
  
  // Returns posts associated with this tag.
  public function getPosts() 
  {
    return $this->posts;
  }
    
  // Adds a post into collection of posts related to this tag.
  public function addPost($post) 
  {
    $this->posts[] = $post;        
  }
}

In the code above, we by analogy define the other side of the relationship and getter/setter methods for retrieving the collection of posts associated with the tag, and adding posts associated with the given tag.

12.6.4. Specifying Entity Locations

To let Doctrine know where to find entities for your Application module (or for another module you have), you add the following lines into your module.config.php file:

<?php
namespace Application;

use Doctrine\ORM\Mapping\Driver\AnnotationDriver;

return [
  // ...
  'doctrine' => [
        'driver' => [
            __NAMESPACE__ . '_driver' => [
                'class' => AnnotationDriver::class,
                'cache' => 'array',
                'paths' => [__DIR__ . '/../src/Entity']
            ],
            'orm_default' => [
                'drivers' => [
                    __NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_driver'
                ]
            ]
        ]
    ]  
];

Above, in line 2, we specify the namespace Application. This should be the name of the current module.

Note that usually we do not specify namespace in config files, but in this particular case it is convenient to do. When we have namespace defined, we can use the __NAMESPACE__ placeholder which expands into that namespace.

In line 8, we have doctrine key, under which we have the driver subkey. In line 13, we tell Doctrine ORM that our entities are stored inside of Entity directory under the module's src directory.


Top