Traits were introduced in PHP 5.4 (6 years ago) as a way to compose classes without relying on inheritance (in opposition to the ability to extends multiple classes like in C++). This feature of PHP is already heavily used in many frameworks just by itself.

However, there is a specific case where traits shine even more: Doctrine entities. In this quick article (August ;) ), I would like to talk about how traits can be extremely useful to compose your Doctrine entities, especially when using the Doctrine extensions.

Using traits in your entities

An entity being a standard PHP class, it is perfectly able to use traits to define additional properties. Moreover, Doctrine is able to handle annotations on traits properties just as if they were properties inside the entity itself.

We can leverage this to create reusable bits of entities. For instance, most of my entities have an ID and an UUID (as I discussed in a related article). Using a trait, I can easily share these two fields across all my entities:

class Project
{
    use EntityIdTrait;
    // ...
}

trait EntityIdTrait
{
    /**
     * The unique auto incremented primary key.
     *
     * @var int|null
     *
     * @ORM\Id
     * @ORM\Column(type="integer", options={"unsigned": true})
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * The public primary identity key.
     *
     * @var UuidInterface
     *
     * @ORM\Column(type="uuid", unique=true)
     */
    protected $uuid;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getUuid(): UuidInterface
    {
        return $this->uuid;
    }
}

Leveraging traits to easily use Doctrine extensions

The Doctrine extensions are a set of extensions designed to help you with common entity-related tasks, such as slugs, timestamps, etc. By using traits, it is possible to ease the usage of these extensions.

EntityNameTrait

For instance, I created a trait defining a generic “name” field and an associated “slug” with it which is going to be automatically populated by the Sluggable Doctrine extension. Additionally, it defines the __toString method on my entities using the name.

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

trait EntityNameTrait
{
    /**
     * @var string|null
     *
     * @ORM\Column(length=100)
     *
     * @Assert\NotBlank
     * @Assert\Length(max=100)
     */
    private $name;

    /**
     * @var string|null
     *
     * @Gedmo\Slug(fields={"name"})
     *
     * @ORM\Column(length=100, unique=true)
     */
    private $slug;

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

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(?string $name): void
    {
        $this->name = $name;
    }

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

    public function setSlug(?string $slug): void
    {
        $this->slug = $slug;
    }
}

EntityTimestampableTrait

In the same idea, I created a trait to leverage the Timestampable Doctrine extension to store the creation date and the last update date of any entity, automatically.

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

trait EntityTimestampableTrait
{
    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime")
     *
     * @Gedmo\Timestampable(on="create")
     */
    protected $createdAt;

    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime")
     *
     * @Gedmo\Timestampable(on="update")
     */
    protected $updatedAt;

    public function getCreatedAt(): \DateTime
    {
        return $this->createdAt;
    }

    public function getUpdatedAt(): \DateTime
    {
        return $this->updatedAt;
    }

    public function setUpdatedAt(\DateTime $updatedAt)
    {
        $this->updatedAt = $updatedAt;
    }
}

EntitySoftDeletableTrait

Finally, this trait uses the SoftDeletable extension to keep in database certain important entities, while treating them as hidden for the application.

use Doctrine\ORM\Mapping as ORM;

trait EntitySoftDeletableTrait
{
    /**
     * @var \DateTime|null
     *
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $deletedAt;

    public function getDeletedAt(): ?\DateTime
    {
        return $this->deletedAt;
    }

    public function isDeleted(): bool
    {
        return $this->deletedAt instanceof \DateTimeInterface;
    }

    public function recover()
    {
        $this->deletedAt = null;
    }
}

I generally don’t use much the other extensions, which is why I haven’t created a trait for them. However, it’s quite straight-forward to do and can be extremely useful: don’t hesitate to build one yourself :) !