Traits to Reduce Model Boilerplate

Traits are a touchy subject in the PHP community. There are many great cases for why they are bad. They can easily be abused and it’s not a matter of pseudo mulitple-inheritance being bad in and of itself, but the bad patterns it can introduce. It’s really just glorified copy-and-paste done by the interpreter on your behalf and that introduces an opportunity.

I would argue for that the strongest case for their existence is reducing the boilerplate in models. This is a pattern I use extensively and has worked wonders for reducing the redundant boilerplate inside my models.

I use Doctrine heavily. A good ORM is the foundation of a great application and the stronger your relationship with that ORM is the stronger your application will be.

If you use an ORM like Doctrine, you have no doubt written code like this dozens (if not hundreds) of times:

/**
 * @var int
 *
 * @ORM\Column(name="id", type="integer", nullable=false)
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="IDENTITY")
 */
protected $id;

That is useless boilerplate. It’s not that having an auto-incrementing primary key named id is useless, but having to express it so verbosely each time is.

The Solution

Instead, I like to use a trait:

<?php

use Doctrine\ORM\Mapping as ORM;

trait Identified
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;

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

    /**
     * @param int $id
     *
     * @return $this
     */
    public function setId($id)
    {
        $this->id = $id;

        return $this;
    }
}

Yes, I clearly prefer fluent model methods and so should you.

Back to the point: From here on, any model that has an auto-incrementing ID (which is 90% of your models) will be able to apply this trait and save you from the property definition and associated getters and setters.

All that takes is to use the trait in the model:

<?php

/**
 * @ORM\Entity
 */
class SomeClass
{
    use Identified;

How About Created and Updated Timestamps?

I’m glad you asked. You can also use this with lifecycle callbacks to handle things like timestamps. This comes with one caveat: You have to indicate on the model using the trait that it has a lifecycle callback. This is handled with a class-level annotation and the use statement for the trait we’ll show later:

<?php

/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class SomeClass
{
    use Timestamped;

After that, you can use a trait to handle the updated and created timestamp:

<?php

use DateTime;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\HasLifecycleCallbacks
 */
trait Timestamped
{
    /**
     * @ORM\Column(name="created_on", type="datetime")
     */
    protected $createdOn;

    /**
     * @ORM\Column(name="updated_on", type="datetime")
     */
    protected $updatedOn;

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

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

    /**
     * @ORM\PrePersist
     * @ORM\PreUpdate
     */
    public function updatedTimestamps()
    {
        $this->updatedOn = new DateTime();

        if (is_null($this->createdOn)) {
            $this->createdOn = new DateTime();
        }
    }
}

From here you’ll never need to worry about the boilerplate of ID, created on or updated on. You tack on those traits and you are good-to-go! It may not seem like a lot at first, but it makes a huge difference in the long run.

While these examples use Doctrine, you can apply the same pattern with other ORMs (mileage may vary).

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.