Symfony World blog is not maintained anymore. Check new sys.exit() programming blog.

symfony event example

Symfony events

see this article in Polish (symfony event na przykładzie sfDoctrineGuardLoginHistoryPlugin)

In a nutshell, events are the mechanisms which connect implemented functionalities with kinda' label (just a chosen name), e.g. "xyz". During the script execution there goes a message: "attention, everyone! xyz!" - and right after that, all functionalities linked to this label are executed. All this is automatic, but you have to define (in a configuration file of the whole project or of a plugin) which events are linked with which functionalities.

For example, a customer submitting an order in an E-commerce application forces the following things to happen:

  • saving all order data in the database
  • sending an E-mail containing all important order data (including payment, shipment, etc.) to the customer
  • assigning the customer to a chosen customer group, depending on the type of assortment he has just bought
  • creating a reminder for the customer service in order to persuade the customer to buy something again - the reminder becomes active for a specific employee e.g. one month after the first order has been submitted
Thanks to this example, we can see how many different mechanisms can be triggered by performing an action. Now the question is - hot to implement it?
  1. of course, we can create elegant methods (in OOP style) and call them each time when dealing with a customer that submits an order
  2. or we can use our custom events to handle this: first we define our events, we link them to corresponding methods in appropriate classes - and finally, in each place where the customer submits the order, we register the event (what will trigger the rest)

In my opinion, the biggest advantage of the second solution is the convenience and flexibility. After we've implemented our custom events and methods, triggering the entire mechanism in a new place in the code is just one line ("attention, everyone! xyz!"). Moreover, we gain the clearness of the code, seen from the top level of the entire project (especially when executing one action has to trigger many other actions to be executed - and this happens very often in big projects). The first solution - which is just a chain of method calls, one after another - can make the businness logic very sophisticated and difficult to understand at a glance. And it will surely make the business logic harder to improve. And finally, each event is some kind of a gate that leads you to anything else in the project. It's just like placing a door somewhere in the wall - you can enter the room very fast and easily, otherwise you would need to ruin the wall to get in.

events in sfDoctrineGuardLoginHistoryPlugin

Symfony has lots of built-in events (symfony 1.4 docs). One of them, user.change_authentication - has been used in sfDoctrineGuardLoginHistoryPlugin. The event is connected to a custom method in the config/sfDoctrineGuardLoginHistoryPluginConfiguration configuration file:

public function initialize()
    $this->dispatcher->connect('user.change_authentication', array('UserLoginHistoryTable', 'writeLoginHistory'));

According to the docs, an event is notified (it takes place) whenever the user authentication status changes. This event is connected with only one method, UserLoginHistoryTable::writeLoginHistory(), which saves login/logout entry in the database for any user:

static public function writeLoginHistory(sfEvent $event)
    $sessionUser = $event->getSubject();
    $params = $event->getParameters();
    if(true === $params['authenticated'])
      $userId = $sessionUser->getGuardUser()->id;
      $sessionUser->setAttribute('user_id', $userId, 'sfDoctrineGuardLoginHistoryPlugin');
      self::createHistoryEntry('login', $userId);
      $userId = $sessionUser->getAttributeHolder()->remove('user_id', null, 'sfDoctrineGuardLoginHistoryPlugin');
      self::createHistoryEntry('logout', $userId);
  protected static function createHistoryEntry($state, $userId)
    $history = new UserLoginHistory();
    $history->state = $state;
    $history->user_id = $userId;
    $history->ip = getenv('HTTP_X_FORWARDED_FOR') ? getenv('HTTP_X_FORWARDED_FOR') : getenv('REMOTE_ADDR');

As a result, we don't have to dig deep into the sfDoctrineGuardPlugin and modify the code to call the method above inside login/logout mechanisms. In fact, we don't have to know anything about . The only thing we need to know is that the user.change_authentication event is notified during user login/logout and we connect our method with this event - and everything works fine. Easy, fast and effective!

Predefined symfony events are some kind of an interface we can use very easily, as we can see in the plugin above (symfony provides us with many doors we can open with no need to destroy the walls to get in). It's a good idea to create custom events in bigger projects and connect them to the mechanisms. Then, enabling/disabling a chosen functionality is just a matter of adding/removing on line of code.

No comments:

Post a Comment