Going Further With Lifecycle Callbacks


Now that we have learned the basics of what Doctrine's Lifecycle Callbacks are and how we can use them, it's time to look at some of the more interesting ways in which they can be used.

However, before we go any further, it's important to pay attention to what Doctrine's Best Practice guidelines tell us about the use of Lifecycle Callbacks. That is, we should be aware that heavy use of lifecycle events can have a performance penalty on your system.

Now, this is one of those issues that likely won't rear its head until your system has grown to a considerable size and complexity. By then, it may be that the very design of your system is structured around these events and changing may be a massive burden.

The simple solution to this potential problem is use a little common sense when implementing Lifecycle Callbacks. Keep operations small and simple.

Improvements in Doctrine 2.4

If you're using Doctrine 2.4 - and if you're not sure, simply do a composer show -i from your command line - then you will have access to various new features, some of which I have covered before.

We can now have either LifecycleEventArgs, or a sub-class there-of, injected for us into the method we have annotated to use one of Doctrine's Lifecycle Callback events:

use Doctrine\Common\Persistence\Event\PreUpdateEventArgs; // don't forget this bit

/**
 * Set updatedAt
 *
 * @ORM\PreUpdate
 */
public function setUpdatedAt(PreUpdateEventArgs $event)
{
    $event->getOldValue();
    $event->getNewValue();
    $event->setNewValue('something');
    $event->hasChangedField('fieldName');

    // * snip *
}

Using a Trait

What happens if we want to set Created At and Updated At fields on most, or all of our entities? The last thing we want is to have to duplicate two properties and at least two methods for every entity.

Enter Traits.

I'm not the biggest user of Traits honestly. But for timestamping, they are a nice thing to have.

Firstly, we need to define a new Trait:

<?php

// src/AppBundle/Entity/Traits/TimestampableTrait.php - or whereever

namespace AppBundle\Entity\Traits;

use Doctrine\ORM\Mapping as ORM;

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

    /**
     * @var \DateTime $updatedAt Updated at
     *
     * @ORM\Column(type="datetime", name="updated_at")
     */
    protected $updatedAt;

    /**
     * Get created at
     *
     * @return \DateTime Created at
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set created at
     *
     * @param \DateTime $createdAt Created at
     *
     * @return $this
     */
    public function setCreatedAt(\DateTime $createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * Get updated at
     *
     * @return \DateTime Updated at
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

    /**
     * Set updated at
     *
     * @param \DateTime $updatedAt Updated at
     *
     * @return $this
     */
    public function setUpdatedAt(\DateTime $updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }
}

And then in your entities:

<?php

use AppBundle\Entity\Traits\TimestampableTrait;
use Blah\Blah;

/**
 * @ORM\Entity()
 * etc
 */
class YourEntity
{
    use TimestampableTrait;

    // * snip *
}

Third Party Alternatives

If writing your own Traits is not something you have time for, or you simply prefer to re-use already available code, then consider using KNP Labs' Doctrine Behaviours which is more than simply Timestampable, there's loggable, sluggable, translatable, and others beside. Very cool, well worth a look.

The only downsides here are you introduce another dependency to your project, add code you may never use, and have to adhere to whatever implementation they give you. But if it suits, then go for it.

Episodes

# Title Duration
1 How To Use Lifecycle Callbacks in Symfony 2 05:23
2 Going Further With Lifecycle Callbacks 05:29