Some months ago I published an article about How to add events to a PHP project (1st EventManager). It should have been the basis for a later article about how to use events to index a site, still to be written.
The 1st EventManager class was ready to use, but some days ago I discovered the Event Dispatcher component of symfony (sfEventDispatcher class), whose Recipes page shows some usage examples. It proved that my EventManager class needed some reworking to have comparable power.
So I developed my 2nd EventManager class, whose differences with respect to the sfEventDispatcher class are explained below.
- EventManager is a Singleton
- EventManager events are strings
- EventManager bind() can attach handlers to regular expressions
- EventManager event handler arguments are loosely structured
- EventManager event handler return value is loosely structured
- EventManager gathers, stores, and eventually returns all handlers results
EventManager is a Singleton
The 1st EventManager I described was a singleton by itself, but the 2nd EventManager extends the Singleton class I talked about in the article Yet Another Singleton Class in PHP (<5.3).
In the Recipes page of the sfEventDispatcher class it is said that a reason for not being it a singleton is that “you might want to have several concurrent event dispatchers in a single PHP request”. That’s not an issue with my Singleton class.
{[ .another_event_manager | 1.hilite(=php,ln-1=) ]}
If you run the previous program, you’ll see nothing, which confirms that AnotherEventManager is not the same EventManager it extends. If you uncomment the commented line, you’ll see
Hello Popeye !
In fact, about a year ago, I used an event manager very similar to sfEventDispatcher (needed injection too) to automatically reindex a website. It worked fine, but I found it a little hard to use and explain to my colleagues.
EventManager events are strings
Strings are perfect for representing events in PHP, and they don’t need to be created before triggering, so this approach helps keeping code clean.
EventManager bind() can attach handlers to regular expressions
Events management is inherently asymmetric: it asks for genericity when you bind handlers and specificity when you call them. That’s why EventManager let’s you attach a handler to a regular expression (which is a special string in PHP). The handler will then get called whenever a triggered event matches the regular expression it is attached to.
EventManager event handler arguments are loosely structured
All the arguments a trigger is called with, are passed by value (see the note below) to each matching handler.
This is very intuitive. For example, if your trigger is
{[ .trigger_args | 1.hilite(=php=) ]}
then a matching handler of yours could be
{[ .handler_args | 1.hilite(=php=) ]}
The first argument of a trigger must be an event, so the first argument of a handler is that event. All other arguments are absolutely free form.
EventManager event handler return value is loosely structured
The return value of a handler can be anything.
As a special case, if a handler wants to stop the propagation of the same event to the rest of the handlers, then it can return an array with two keys: ‘result’ and ‘stopPropagation’. The former’s value will be the real result of the handler (and the trigger) and the latter’s one, if true, will cause EventManager to quit immediately, without further handler processing.
EventManager gathers, stores, and eventually returns all handlers results
Results are pushed on a stack, and eventually the stack is returned to the trigger caller. If the trigger caller wants to know the last result all it needs to do is pop the stack.
The current stack will also be passed by reference (see the note below) to each handler. The last argument a handler receives is in fact the stack that holds the results from all previous handlers, plus a slot (on top) with a bit more data. Those data are just one, for now. It’s the event template (normal string or regular expression) the current handler was attached to.
Two use cases for the stack follow:
- If a handler wants to cancel it’s execution so that it appears as if it was never called, then all it needs to do is pop the stack and return.
- If a handler wants to act on the result of a previous handler, then all it needs to do is get to the desired element of the stack.
When filtering, the result of a handler feeds the next handler in the pipeline. Such a handler cannot know a priori if it is being executed first or not, so it needs to decide if it takes the input from an argument or from a previous result. This is easy to do using the stack, but EventManager offers a special static method that does it all at once:
{[ .pipeline | 1.hilite(=php=) ]}
When using stopPropagation, the neat result value is pushed on the stack.
Arguments are passed as described, no matter how parameters are declared in the handler: this is a known PHP feature.
—
Recipes
Now I’ll show what the recipes of sfEventDispatcher become when using EventManager.
- Action Wrapping, called Doing something before or after a Method Call in sfEventDispatcher docs
- Runtime Class Extension, called Adding Methods to a Class in sfEventDispatcher docs
- Filters Pipeline, called Modifying Arguments in sfEventDispatcher docs
Action Wrapping
{[ .wrapping_actions | 1.hilite(=php,ln-1=) ]}
Trace
{[ .wrapping_actions_result /precode.php ]}
Runtime Class Extension
{[ .runtime_class_extension | 1.hilite(=php,ln-1=) ]}
Trace
{[ .runtime_class_extension_result /precode.php ]}
Filters Pipeline
{[ .filters_pipeline | 1.hilite(=php,ln-1=) ]}
Output
a *quick* red fox jumps OVER a _lazy_ brown dog.
—
EventManager class
Last but not least, here is the code of the EventManager class
{[ .event_manager | 1.hilite(=php,ln-1=) ]}