PHP Event-Listener best-practice implementation
Solution 1:
Well, there's really three different ways of doing this from an implementation perspective (note that these are OO design patterns, but you could implement them functionally or procedurally if you wanted to).
1. Observer Pattern
You can implement the Observer Pattern. Basically, you'd have each thing that can raise events be a subject. Then the classes/code you want to listen binds to what it wants to listen to specifically. So let's say you have a controller called Foo
. If you wanted to listen to it, you could call $fooController->attach($observer);
. Then, whenever the controller wanted to say something, it would dispatch the event to all of the observers.
This is really well suited for a notification system (to extend what classes are doing). It's not as well suited for modifying the behavior of code in real time.
2. Decorator Pattern You can also implement the Decorator Pattern. Basically, you take the object that you want to modify, and "wrap" it in a new object that does what you want to change. This is really well suited for modifying and extending the behavior (since you can selectively override functionality from the wrapped class).
This works very well if you have defined interfaces and expect objects to conform to them. If you don't have interfaces (or don't use them properly), most of what the decorator pattern can do for you will be lost.
Also note that this really isn't a way of doing events, it's a way of modifying object behavior.
3. Mediator Pattern
You could also use a Mediator. Basically, you'd have one global mediator that keeps track of your listeners. When you want to trigger an event, you send the event to the mediator. The mediator can then keep track of which listening objects want to receive that event, and pass the message along properly.
This has the advantage of being central. Meaning multiple senders can send the same event, and to the listeners it doesn't make a difference who sent it...
I expanded on this topic in a blog post.
Solution 2:
/*
Example 1:
event::bind('blog.post.create', function($args = array())
{
mail('[email protected]', 'Blog Post Published', $args['name'] . ' has been published');
});
Example 2:
event::trigger('blog.post.create', $postInfo);
*/
class event
{
public static $events = array();
public static function trigger($event, $args = array())
{
if(isset(self::$events[$event]))
{
foreach(self::$events[$event] as $func)
{
call_user_func($func, $args);
}
}
}
public static function bind($event, Closure $func)
{
self::$events[$event][] = $func;
}
}
Solution 3:
This is how i did it in a couple of projects
All objects are created with a constructor function instead of new
operator.
$obj = _new('SomeClass', $x, $y); // instead of $obj = new SomeClass($x, $y);
this has many advantages compared to raw new
, from an event handling standpoint it's important that _new()
maintains a list of all created objects.
There's also a global function send($message, $params)
that iterates though this list and, if an object exposes a method "on_$message", calls this method, passing params:
function send() {
$_ = func_get_args();
$m = "on_" . array_shift($_);
foreach($_all_objects as $obj)
if(method_exists($obj, $m))
call_user_func_array(array($obj, $m), $_);
}
So, for example, send('load')
will call on_load
method for every object that has it defined.
Solution 4:
If you're using PHP 5.3 (and thus have access to rich closures), the event/filters system in Lithium is what I'd use as a basis for AOP design in PHP.