Aug 04, 2014
Table of contents:
A common requirement when building applications is the notion that something must be triggered as a consequence of something else happening within the system.
This could be as simply as keeping an updated_at
field up-to-date whenever a record in the database is changed, or perhaps sending an email whenever a new user registers for your application.
Doctrine 2 comes bundled with an event system to allow you to implement events as part of your project.
In this week’s tutorial we are going to look at Doctrine 2’s event system and the lifecycle events that can be hooked on to as part of your entity management.
Events are a way of triggering certain actions that should be executed as a consequence of that particular event occurring.
For example, when a new user registers for your application you might want to send them a welcome email.
Typically you wouldn’t want the code to register a new user and the code to send a new email coupled together.
Instead an event system can be used to trigger sending a welcome email whenever a new user registers. This allows you to keep each part of your application to a single responsibility whilst also making it very easy to add or remove what should happen on the occurrence of an event without ever having to touch your existing code.
If you would like to read more about events and how they are technically implemented have a read of the following two previous posts in this series:
Doctrine triggers a number of events that you can hook on to or listen for. The Doctrine events you have available to you are:
Note: I’ve lifted these descriptions straight from the Doctrine documentation.
You can hook on to these events in two different ways. You can either use Lifecycle Callbacks on your entities or Lifecycle Event Listeners, which are dedicated objects. We’ll look at how to implement both.
The first and easiest method for hooking on to Doctrine Lifecycle events are known as Lifecycle Callbacks. These are simply methods that are implement on your entity.
For example, imagine if we had a User
entity and we wanted to maintain an updated_at
timestamp whenever that user updater her profile.
First we would start with our basic entity class:
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string")
*/
private $name;
}
The first thing we need to do is define the $updatedAt
class property:
/**
* @ORM\Column(name="updated_at", type="datetime")
* @var \DateTime
*/
private $updatedAt;
Next we can define a setUpdatedAt()
method that will set the property to a new DateTime
instance:
public function setUpdatedAt()
{
$this->updatedAt = new DataTime;
}
Now in theory we could call the setUpdatedAt()
method in our codebase whenever we wanted to set the $updateAt
property.
However that would be a terrible solution because we would have to litter our codebase with calls to the method. If we wanted to change this behaviour in the future we would have to find all occurrences of that method call. So we really don’t want to do that!
Instead we can tell Doctrine to automatically call the method whenever this entity is updated.
First add the following annotation to the entity:
/**
* @ORM\HasLifecycleCallbacks()
*/
Next add the following annotation to the setUpdatedAt()
method:
/**
* @ORM\PreUpdate
*/
public function setUpdatedAt()
{
$this->updatedAt = new DataTime;
}
HasLifecycleCallbacks()
notifies Doctrine that this entity should listen out for callback events. You only need to have this annotation on your entity if you are using callback events.
@ORM\PreUpdate
tells Doctrine to automatically call this method on the PreUpdate
event.
Now whenever you update a User
entity, the updated_at
table column for that record will automatically be set! Pretty nice huh?
For really common functionality like maintaining updated at or created at properties, instead of implementing this code on each of your entities, you’re better off creating a trait that you can simply include on the entities that require it.
Or even better yet, if you’re using Mitchell van Wijngaarden’s Doctrine 2 for Laravel package, you can use the Timestamps trait that is already created for you.
Lifecycle Callbacks are a great solution for a number of situations you will face.
However, Lifecycle Callbacks can also bloat your entity classes with extra methods that really shouldn’t be there.
In the example above, using setUpdatedAt()
to set the $updateAt
property is fair enough because you are just setting a property on the entity automatically.
But what if you wanted to send an email? We definitely do not want to couple our entity to the ability to send emails!
Instead we can use Lifecycle Event Listeners. A Lifecycle Event Listener is an encapsulated object that listens for an event trigger. This means you can create these little nuggets of code to perform extraneous actions that are triggered by Doctrine’s Event system.
For example, to send an email whenever a user is created we could create a SendWelcomeEmail
class that would be triggered on the postPersist
event. This class would encapsulate the logic for sending the welcome email.
Doctrine has two ways in which you could implement this functionality, using either a Listener of a Subscriber.
Using a Listener would look like this:
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class SendWelcomeEmailListener
{
public function postPersist(LifecycleEventArgs $event)
{
// Send welcome email
}
}
You would then register the event using Doctrine’s Event manager like this:
// $em is an instance of the Event Manager
$em->getEventManager()->addEventListener(
[Event::postPersist],
new SendWelcomeEmailListener()
);
Alternatively you can create an Event Subscriber, like this:
use Doctrine\ORM\Events;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class SendWelcomeEmailSubscriber implements EventSubscriber
{
public function postPersist(LifecycleEventArgs $event)
{
// Send welcome email
}
public function getSubscribedEvents()
{
return [Events::postPersist];
}
}
You would then register the Event Subscriber like this:
// $em is an instance of the Event Manager
$em->getEventManager()->addEventSubscriber(new SendWelcomeEmailSubscriber());
As you can see, the Event Subscriber implements the EventSubscriber
interface and has a getSubscribedEvents()
that returns an array of events that should be subscribed to. This is in contrast to an Event Listener where you would subscribe to the events when you register the listener on the Event Manager.
As far as I know, you can use these two different methods interchangeably. Personally I prefer the Event Subscriber method as it forces you to define the events it should listen for inside the class.
Now that we have a class to deal with the logic of the event, we can inject dependencies into the class to deal with sending the email:
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
This means we don’t have to couple the event with the mailer and we can very easily switch out the implementation or mock it for testing.
Next we can write the logic to send the email in the postPersist()
method:
public function postPersist(LifecycleEventArgs $event)
{
$em = $event->getEntityManager();
$entity = $event->getEntity();
}
First we can get the Entity Manger and an instance of the Entity from the LifecycleEventArgs
that is passed in as an argument.
Next we can send the email using the Mailer
object that we passed in as a dependency:
$mailer->send("welcome", $data, function ($msg) use ($entity) {
$message->to($entity->email, $entity->name)->subject("Welcome!");
});
However, this Event Subscriber will be triggered for every entity that is persisted by Doctrine. This means if we only wanted to send emails to new User
entities, we would have to include an if ()
statement:
if (!$entity instanceof User) {
return;
}
If you wanted to send welcome emails to multiple different types of entity you could type hint for an interface instead:
if ( ! $entity->instanceOf Welcomeable ) { return; }
If you were using an Event Listener or Subscriber to update properties of the entity it gets a little bit more complicated.
To do that we need to grab an instance of Doctrine’s UnitOfWork
. The UnitOfWork
is how the Entity Manager is able to process the changes to your entities.
$uow = $event->getEntityManager()->getUnitOfWork();
$uow->recomputeSingleEntityChangeSet(
$em->getClassMetaData(get_class($entity)),
$entity
);
This is basically telling Doctrine that the instance that is currently being held in the Entity Manager should be replaced with the one that is passed in here with the updated properties.
Doctrine’s Unit Of Work is really the power behind Doctrine. I won’t get into the finer technical details of how the Unit Of Work operates because it is both out of the scope of this article and way over my head. I’m sure we will need to take a deeper look into how it works in future articles in this series.
Doctrine has a very good event’s system that allows you to easily hook on and perform certain actions on specific database events.
However, Doctrine events will be triggered for all entities in your project. This is either a really good thing or a really bad thing.
It’s good because it makes it really easy to maintain entity properties like updated_at
or created_at
across all of your entities. It’s really easy to create a trait and have all of your entities automatically pick up that functionality.
However I don’t think Doctrine’s events are particularly great for triggering emails or other none database stuff actions. As you can see from the example above, having to type hint for certain entities gets messy.
In reality, I would never actually use the email example that I’ve shown in this article. Domain Events should be completely separate from infrastructure events. Hopefully this contrasting example will show you that Doctrine Events are very different to Domain Events and should not be used interchangeably.
Instead we can use an external event system that allows us to keep our Domain Events out of the scope of Doctrine. We’ll be looking at how to do that in a couple of weeks.
This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.