Using the symfony Event System

February 21, 2009
Alvaro Videla

In this tutorial I would like to show how you can add flexibility to your symfony applications using the symfony built-in event system. For this
example let's imagine that we have a website where the user can rate pictures from other users in the system. Every time the user rates a
picture, some actions should happen according to the application business rules:

The user will gain points for his action

The session should be updated indicating the amount of images rated by the user

The view cache must be cleared for the template that shows the amount of user rated images

While we can override the Image::save() method to add a hook, so every time the user rates an image we perform those three actions, this
can bring some unwanted consequences. To name a few: If the user uploads an image, when we store the object in database we will
unnecessarily clear the view cache and store data in the session. Also, if we have a backend to administer the images, every time a website
admin review an image the overridden save() method will be called, with all the extra actions.

Symfony Event System to the rescue

Since version 1.1, symfony provides an event notification system. Basically, the framework classes dispatch events at certain moments of
the application lifetime. We can register our own listeners to act upon event notifications. Also we can even create and dispatch our
custom events.

To read more about the symfony event system, refer to the following links:

This will fire an event with the name images.image_rated that will have as subject the action where it was generated. We will use the
subject to later retrieve information related to the context where the event subject occurred.

The sfEvent constructor expects up to three parameters in the following order: event subject, event name and an optional array of parameter
which can be used by the event handler. Because the sfEvent implements the ArrayAccess interface we can access the parameters with ease like
this: $event['my_parameter'].

The next step is to actually listen to this event. We can add listeners in several places along a symfony application. For this example we
will do it in the application configuration file. So, let's register the listeners:

As you can see, the application configuration class has a member that keeps a reference to the symfony event dispatcher. There we tell it
that we want to be notified when an event of the type images.image_rated is fired. Then, we will create a UsersManager class that will
provide the methods to listen to the events. Before going into the UsersManager class code we should talk about the sfEventDispatcher
class.

From the symfony book we know that this class provides the mechanisms to add and remove listeners and to notify events. Let's see some of
the sfEventDispatcher public methods:

public function connect($name, $listener)

We used this method in the frontendConfiguration class to register listeners to our custom event. The first parameter is an event name
(images.image_rated in our case) and the second one a listener which should be a variable of the callback pseudo PHP type.

public function disconnect($name, $listener)

The expected parameters are the same as the previous method, but this time we use it to remove a listener. If we have the case that a
specific module of our application doesn't need to listen to a particular event we can add a config.php file inside the module config
folder with the following code:

In the addPointsToUser() method, we retrieve the actual user points from the session, we add more points, and then we store them in to the
database.

The updateSessionPoints() follows a similar mechanism but update the user session.

If your are wondering how do we access the symfony session user, the answer is in the magic behind the symfony events system. If you recall
from the code to notify in our action, we provided the current action instance as the subject of our custom event. In symfony, the
action has access to the session user by calling $this->getUser() which is what we do in our example.

The last event listener that we registered was UsersManager::clearRatePartial(), where we get an instance of the View Cache Manager and we
tell it to clear the required cache.

Conclusion

Symfony event system provides a lot of capabilities to your applications. It can be used to build highly decoupled systems, leaving away glue
code. After you get used to this techniques, it will be easier to build slim controllers that maximize the power of MVC. Also keep in mind that,
as explained on the symfony book, you can even adapt at run time the way the framework works, making it highly customizable.

Although it does slim controllers, there is a trade off of moving some important logic like incrementing the users points into the "background" hidden quite deep inside the app which is not always a good thing, especially when working in teams.

I think it's important to keep any interactions with your model in the controller for clarity.

Of course having the logic in one place has advantages when working in teams and without a given design model, but on the other side events are great to decouple some logic, that is not related to the core.

You for example might want to be able to activate/deactivate some features in your application like a facebok activity feed or something like that. But if you want to deactivate that feature due to too high perfomance load or bugs, the hardcoding it in the controller will force you to go directly to the controller and comment the feature logic. Using Events you could just disconnect the eventlistener. I like that idea very much!

Having read the last blog articles about the event system I added a feature suggestion to uservoice yesterday ;)
http://symfony.uservoice.com/pages/symfony/suggestions/126150-add-more-sfevents-to-core-classes

This idea with sfEventDispatcher is only an implementation of famous design pattern called "observer pattern". The great feature for me is: the symfony core classes do fire events so I can observe it and add custom action. It makes symfony more flexible and extensible.

But take care with firing own events: If you fire over 100 events and you will lose the overview and you can make your errors untraceable. DonÂ´t forget to keep records which events you did fire and which objects you passed into sfEvent.

@Fabian Spillner: But the issue is that an operation on the model can be done by many different ways.

Let's consider the case of the event fired when an article is deleted.
Of course, you can notify the event in every action/component class that delete an article. But it goes against the DRY concept.

And sometime, there's hardly no other way to do it.
In your application, your client wants that if an author is removed, all his articles are also deleted.
Then how will you fire the article deletion event ? By notifying it also in every action/component that delete an author ?
For me the good place for these event notification is the model classes.

@Fabian and Ã‰ric: I must agree with Ã‰ric, this definitely belongs to the model ("model" not only stands for data model but also for application model). One of the most important rules using the MVC pattern is: keep the controller as small as possible. Application logic always has to go to the model, not the controller or the view. Even if it is not for DRY, the maintainability is much better if you follow this rule (it took me several years on several projects in both Java and PHP to really understand and accept this though).

To the events system in general: I've been using the observer pattern quite extensively in Java and I love it - it is a really powerful thing to use (if used correctly) and it's great for keeping applications (and application models) separated. As with anything else there are ways to misuse it.