PHP
Article

Using Traits in Doctrine Entities

By Nicolas Scolari

Since PHP 5.4.0, PHP supports a pretty way to reuse code called “Traits” – a set of methods that you can include within another class in order not to repeat yourself. You can read more about traits in previously published SitePoint posts: here, here and here.

Copy - Flat icon for web and mobile apps

Today, I am going to show you how they can be used with Doctrine ORM in a Symfony Environment.

Trait Basics

<?php
trait ExampleTrait {
    public function sayHello() {
        echo "Hello";
    }
}

class A {
    use ExampleTrait;
}

class B {
    use ExampleTrait;
}

$one = new A();
$one->sayHello();    /* return `Hello` */

$two = new B();
$two->sayHello();    /* return `Hello`, too */

As we can see, a basic method sayHello() is declared inside a Trait that is implemented by both A and B classes with a use statement. Easy, right? This example is really short but it should give you the basic knowledge to work with Traits.

If you are interested in Traits, I recommend you read the official documentation and previously published SitePoint posts here, here and here, to fully grasp the concept.
Allow me to warn you about the fact that many people tend not to see a difference between Traits and Interfaces. Here is a pragmatic explanation:

An interface is a contract that says “this object is able to do this thing”, whereas a Trait is giving the object the ability to do the thing.

For a more in-depth explanation, feel free to take a look at this insightful post by Philip Brown, which the previous quote comes from.

When it comes to organizing one’s database architecture, it is not uncommon to face code duplication. As an example, suppose we have to develop the usual blog application. At some point, it is likely that we are going to create a basic Article entity and probably a Comment entity as well.

Both entities would benefit from having created_at and updated_at fields (so we can sort results on those columns later). But before digging into traits, let’s see how we could build those entities in Doctrine without them.

Step 1: create the entities

Article entity

<?php
namespace Blog\AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="article")
 * @ORM\Entity(repositoryClass="Blog\AppBundle\Entity\ArticleRepository")
 */
class Article
{
    /**
     * @ORM\Column(name="idArticle" type="integer")
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /* Other properties you need in your entity: $title, $content, $author...  */

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

    /** @ORM\Column(name="updated_at" type="datetime") */
    private $updatedAt;

   /* Getters & Setters */
}

Comment entity

<?php
namespace Blog\AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="comment")
 * @ORM\Entity(repositoryClass="Blog\AppBundle\Entity\CommentRepository")
 */
class Comment
{
    /**
     * @ORM\Column(name="idComment" type="integer")
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /* Other properties you need in your entity */

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

    /** @ORM\Column(name="updated_at" type="datetime") */
    private $updatedAt;

    /* Getters & Setters */
}

The same properties $createdAt and $updatedAt are included in both classes. This is far from DRY. Would Traits be able to help us clean this code up?

Step 2: create the Trait

<?php
// src/Blog/AppBundle/Entity/Traits/TimestampableTrait.php

namespace Blog\AppBundle\Entity\Traits;

use Doctrine\ORM\Mapping as ORM;

trait TimestampableTrait
{
    /**
     * @var datetime $createdAt
     *
     * @ORM\Column(name="created_at", type="datetime")
     */
    private $createdAt;

    /**
     * @var datetime $updatedAt
     *
     * @ORM\Column(name="updated_at", type="datetime")
     */
    private $updatedAt;


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

    /**
     * Set createdAt
     *
     * @param datetime $createdAt
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * Get updatedAt
     *
     * @return datetime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

    /**
     * Set updatedAt
     *
     * @param datetime $updatedAt
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }
}

Here is a pretty trait file into which we have moved the initial duplicated code. Both $createdAt and $updatedAt as well as all the associated methods are now separated from the entities. As a result, it will be much easier to use them somewhere else. Remember the introduction section with the keyword use.

Step 3: refactor the entities

Article entity

<?php
// src/Blog/AppBundle/Entity/Article.php

namespace Blog\AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Blog\AppBundle\Entity\Traits\TimestampableTrait;

class Article
{
    use TimestampableTrait;

    /**
     * @ORM\Column(name="idArticle" type="integer")
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /* Other properties you need in your entity */

    /* Getters & Setters */
}

Comment entity

<?php
// src/Blog/AppBundle/Entity/Comment.php

namespace Blog\AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Blog\AppBundle\Entity\Traits\TimestampableTrait;

/**
 * @ORM\Table(name="comment")
 * @ORM\Entity(repositoryClass="Blog\AppBundle\Entity\CommentRepository")
 */
class Comment
{
    use TimestampableTrait;

    /**
     * @ORM\Column(name="idComment" type="integer")
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /* Other properties you need in your entity */

    /* Getters & Setters */
}

Done! Let’s play with the command line. First, let’s create the entities in our database:

php app/console doctrine:schema:create

This command would yield:

`Article Entity`
	
	| idArticle | *All our other fields...* | created_at | updated_at |
	|-----------|---------------------------|------------|------------|
	
	`Comment Entity`
	
	| idComment | *All our other fields...* | created_at | updated_at |
	|-----------|---------------------------|------------|------------|

Now, if you want to create new objects from these classes, you would find that they both have the common methods available:

<?php
    $article = new Article();
    $article->setUpdatedAt(new \Datetime('now'));

    $comment = new Comment();
    $comment->setUpdatedAt(new \Datetime('now'));
?>

Obviously, we are now ready to persist the data.

Going further

Currently, in the Symfony sphere, many bundles and extensions tend to stick to this way of doing things. The DoctrineBehaviors library from KNPLabs provides a great collection of Traits for entities and repositories. In the same state of mind, I recommend you have an in-depth look at the well known DoctrineExtensions bundle and especially everything about the Timestampable behavior extension.

Final Thoughts

Traits are not difficult to absorb. They are an excellent way to produce lighter and more flexible code. Be careful not to abuse them: sometimes, it is better to construct a unique class implementation. I can’t stress enough how crucial it is to take enough time in order to properly design your app. Give them a go if you think they could help you. Create yours, test them and tell us how you used them!

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

Comments
s_molinari

Nice article Nicolas.

How would the traits work with mapping metadata? Annotations is pretty clear, but how about mappings in XML or YAML?

Scott

michaelperrin

Hi Nicolas,

If you're using YML or XML annotations, you will have to duplicate column declarations in the YML or XML mapping files, but you can still use the traits to avoid declaring properties and getters/setters in each of your entities.

You may be interested in a great new feature of Doctrine ORM 2.5 (not released yet) called Embeddables. There is a nice example about it in the documentation: http://doctrine-orm.readthedocs.org/en/latest/tutorials/embeddables.html

scoolnico

Hi guys,
thanks for reading this article.

I totally agree with Michael's answer concerning an XML or YAML mapping.
Do not forget: a trait is not an existing thing per se, it's an equivalent copy-pasted code that can be reused in different classes.

@michaelperrin: this Doctrine feature is awesome! Thank you for the information.

Commocore

Well, I think this is really the first use case of traits. I'm not a fan of traits (it seems to be some kind of anti-pattern if used not with caution) so, this example is an excellent one to show traits in action. Thanks!

s_molinari

I just reread the article and now realize my question above was complete nonsense. If you guys see I write nonsense, please don't be afraid to tell me so. smiley

Scott

aazon

The only issue with traits in this case is that IDE (e.g. PhpStorm) will not be able to source to class methods when you are in trait and will underline it as it were undefined.

class Foo
{
    use Bar;

    private $someProperty;
}

trait Bar
{
    public function getSomeProperty()
    {
        return $this->someProperty;
    }
}

In this particular example $this->someProperty will be underlined;

swader

@aazon to get around this, use the @property tag, like so.

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.