Using PHP closures with the observer pattern

January 28, 2013 | Leave a comment

PHP 5.3 introduced the concept of anonymous functions, often called closures. These have been present in languages like JavaScript for a long time, and have been incorporated into PHP as part of its ongoing transformation into a first-class web programming language.

Simply put, they allow you to assign the workings of a function to a variable. For example:

$myFunction = function() {
    return true;
}

This simplifies a lot of common programming tasks. For example, prior to PHP 5.3, you might have used the array_walk function to perform operations on the entities in an array as follows:

function processEntity(&$entity) {
    // perform some action on $entity
}

$myArray = array('one','two','three','foo','bar');

array_walk($myArray, 'processEntity');

Whereas now you can go ahead and perform your entire processing step in one statement:

$myArray = array('one','two','three','foo','bar');

array_walk($myArray, function(&$entity) {
    // perform some action on $entity
});

This works because in PHP, an anonymous function is a callable: it can be included wherever the name of a function was previously requested, without any further modifications to your code.

This is handy in itself for many uses, but it comes into its own when you combine it with the observer pattern. Here, observer methods listen for an event to be triggered, and may alter the subject of the event (where appropriate). I’m a big user of observers: Elgg’s architecture has depended on them since 2004, and in every project I’ve worked on since, observers have been called on system events (for example to run code when an object is saved or updated), and actions (for example to run code when a form action is fired, to log the user on, post some content, or some other action). They allow you to create simple object-based architectures where the components are logically separate but each section can communicate with each other section.

While I used to roll my own observer code, lately I’ve fallen in love with Symfony’s Event Dispatcher component (not least because it does pretty much what I was doing before, with richer functionality and code I don’t have to maintain myself!).

Events and actions are referenced with a unique string. For this example, I’ll use unique.event.name, and attach a listener callable that will be triggered whenever the event is dispatched, as follows:

$eventDispatcher->addListener('unique.event.name', $callable);

(There’s also an optional third parameter, $priority, which is an integer that indicates priority. Higher integers have a higher priority.)

Remember that a callable can be a string containing a function name, an array referencing a class method, or an object with an __invoke method. But because an anonymous function is also a callable, we can stick a function right there in the listener call:

$eventDispatcher->addListener('unique.event.name', function($event) {
    // Do something with $event
});

Invoking the event is as simple as calling:

$eventDispatcher->dispatch('unique.event.name', $event);

$event here is an object of class Symfony\Component\EventDispatcher\Event, so you could make your life easy and create subclasses for different kinds of events that carry payloads of useful data:

class exampleEvent extends Symfony\Component\EventDispatcher\Event {
    public $simplePayload;
    function __construct($simplePayload) {
        $this->simplePayload = $simplePayload;
    }
}

Then you can trigger the event and attach some payload data in one line:

$eventDispatcher->dispatch('unique.event.name', new exampleEvent($someData));

Each listener callable has direct access to this event object, and can modify it in turn. The object also provides access to the event dispatcher via $event->getDispatcher(), which can launch more nested events, or halt propagation to the next listener in the chain.

By creating different subclasses of Event for different event types, you can easily attach special event functionality or payload support for those instances. I can easily imagine defining UserLoginEvent, UserLogoutEvent and UserSaveEvent, for example. Event listeners can then check for event type (if they need to) by checking the class of the event.

By using anonymous functions for this process, you get to keep your listener and logic code in the same place, and write fewer lines of code overall, while making the most of a very powerful software development pattern.

Related entries

0 Comments

No comments yet.

Leave a comment