Using Traits in Doctrine Entities

Share this article

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!

Frequently Asked Questions (FAQs) about Using Traits in Doctrine Entities

What are the benefits of using traits in Doctrine entities?

Traits in Doctrine entities provide a way to reuse code in languages like PHP that do not support multiple inheritances. They allow you to create reusable code snippets that can be inserted into different classes to provide additional functionality. This can lead to cleaner, more maintainable code, as you can avoid duplicating code across multiple classes. Traits can also be used to override methods in the classes they are used in, providing a powerful tool for modifying behavior in a flexible way.

How do I use traits in Doctrine entities?

To use a trait in a Doctrine entity, you first need to define the trait. This is done using the trait keyword, followed by the name of the trait and a block of code containing the methods and properties that the trait provides. Once the trait is defined, you can use it in a class by adding a use statement inside the class definition, followed by the name of the trait. This will make all the methods and properties of the trait available in the class.

Can I use multiple traits in a single Doctrine entity?

Yes, you can use multiple traits in a single Doctrine entity. This is done by adding multiple use statements inside the class definition, each followed by the name of a different trait. The methods and properties of all the traits will be available in the class. If there is a naming conflict between methods or properties in different traits, you can resolve it using the insteadof and as operators.

Can traits have services injected?

Traits themselves cannot have services injected directly, as they are not classes and do not support constructor injection. However, you can inject services into the classes that use the traits. The methods of the trait can then access these services through the class.

Can traits override methods in Doctrine entities?

Yes, traits can override methods in the classes they are used in. This is done by defining a method in the trait with the same name as a method in the class. When the method is called on an object of the class, the version in the trait will be used instead of the version in the class.

Can I use traits in conjunction with inheritance?

Yes, you can use traits in conjunction with inheritance. A class can inherit from a parent class and also use one or more traits. The methods and properties of the parent class and the traits will all be available in the class. If there is a naming conflict between methods or properties in the parent class and a trait, the version in the trait will be used.

Are there any limitations or drawbacks to using traits?

While traits provide a powerful tool for code reuse and flexibility, they also have some limitations and potential drawbacks. One limitation is that traits cannot be instantiated on their own – they can only be used within a class. Also, if multiple traits define a method with the same name, there can be naming conflicts that need to be resolved manually. Overuse of traits can also lead to code that is difficult to understand and maintain, so they should be used judiciously.

How do I test Doctrine entities that use traits?

Testing Doctrine entities that use traits is similar to testing regular Doctrine entities. You can create unit tests that instantiate the entity and call its methods, checking that they behave as expected. If a trait provides additional methods, you can test these in the same way. If a trait overrides a method in the entity, you should test both the original version of the method (by testing it on an entity that does not use the trait) and the overridden version (by testing it on an entity that uses the trait).

Can I use traits in Doctrine entities with Symfony?

Yes, you can use traits in Doctrine entities with Symfony. Symfony’s Doctrine integration supports the use of traits in entities. You can define your traits, use them in your entities, and Symfony will recognize and use them when working with your entities.

How do I debug issues with traits in Doctrine entities?

Debugging issues with traits in Doctrine entities is similar to debugging issues with regular Doctrine entities. You can use tools like Xdebug and var_dump() to inspect the state of your entities and see what methods and properties they have. If a method is not behaving as expected, you can check whether it is defined in the entity itself, in a trait, or in a parent class, and debug accordingly.

Nicolas ScolariNicolas Scolari
View Author

Nicolas Scolari is a back-end developer from France specialized in PHP and currently actively experimenting with Symfony 2. He also loves fiddling with front-end technologies, trying to mix both sides in order to become a better guy.

BrunoSdoctrineOOPHPormPHPsymfonytimestamptraittraits
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form