Decoupling with Symfony Events - Part I

Already familiar with the Symfony Event Dispather? Skip the basics

It's monday and your client tells you:

When a user places an order, the app should send a notification to the administrator.

The most straight forward way to implement that in Symfony is to go in the controller or service that places the order (the action) and write code that notifies the administrator (the consequence).

By structuring an application like that, the code handling orders would be very coupled to the one responsible for notifications.

Although these two concerns are linked, they should not be coupled. This will make an application difficult to maintain and to evolve.

Events to the rescue

Events are messages that link actions to consequences in your application while keeping them independent.

Dispatching an event is identifying a meaningful domain action. The event provides an entry point for every consequence that reacts to this action.

Let's take our previous example and add one more feature:

The app should also generate an invoice PDF file and save it on the server when an order is placed.

Given this highly coupled code:

Refactoring the same feature with events would look like:

Now the action and its two consequences are independent, linked by an event.

The benefices of separating concerns

Team work: The developer working on the Notifier feature won't depend on the team that provides the orders workflow. They can work in parallel, or not, and avoid being slowed down by conflicts.

Evolutivity: It's very easy to code a new consequence without affecting the action. By adding a listener, you can plug literally any process on your domain event: logging, exporting datas, building some cache, sending emails...

Flexibilty: Consequences can be activated and deactivated by configuration or context very simply.

Getting it done

Fortunately Symfony comes with a nice Event Dispatcher component.

The documentation is thorough and gives complete implementation examples, you should read it.

Once you're familiar with the tools, design your workflow by identifying your domain actions and consequences and naming your domain events.

Finally, proceed with the implementation:

Create and dispatch domain events

Your domain events are messages meant to transport any relevant information about what happened. They should be emitted from your controllers and services where the action occurs.

The only requirement for an event is to be an instance of Symfony\Component\EventDispatcher\Event.

You can directly use this class by omitting the event object parameter:

$dispatcher->dispatch('my_event');

But you may want to write your own classes, to structure your events and give them custom properties and methods. Just have your class extends the default Symfony\Component\EventDispatcher\Event class and dispatch an instance of your class:

$dispatcher->dispatch('my_event', new MyDomainEvent());

Good practice: Symfony recommends that you reference all domain events in a static class. It will avoid some typos ;)

ProTip: Several events can use the same class, if their behavior is similar. Eg: you can dispatch order.registered, order.shipped, order.arrived events using the same OrderStatusChangedEvent class.

Setup your workflow

Now all you need is to connect actions to consequences using Listeners (or Subscribers).

The cookbook for subscribers and listeners will tell you everything you need to know.

How about Doctrine events?

Doctrine comes with its own event system, how do we deal with these?