50

I am trying to create a CMS-like system in PHP. making it as modular and extendable as possible.

Could someone offer me the best-practice scenario of creating a event-listener system in PHP (a very simplified version of Drupal system for example), creating hooks and implementing them in a short example would also be nice.

Y.H.
  • 2,687
  • 1
  • 29
  • 38
  • 1
    This might be of interest to you: http://components.symfony-project.org/event-dispatcher/ – Craige Dec 17 '10 at 14:15

4 Answers4

139

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.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • 3
    For a real implementation to explore, WordPress implements the Observer Pattern using add_action and a few other things. It's not beautiful but it tends to work. – CaseySoftware Dec 17 '10 at 14:32
  • 5
    @Casey: That's the Mediator pattern, not an Observer pattern. Any time you aren't attaching to the thing you want to listen to directly, it's the mediator pattern. But it is an example of a procedural implementation of an OO design pattern... So that is a very useful comment... – ircmaxell Dec 17 '10 at 14:35
  • thanks for the thorough explanation, I wonder if you can provide me with a simple concrete example of the Observer pattern – Y.H. Dec 19 '10 at 11:55
  • Thanks for this answer and your linked article – Emarco Mar 04 '16 at 15:56
35
/*
 Example 1: 
 event::bind('blog.post.create', function($args = array())
 {
    mail('myself@me.com', '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;
    }
}
Codebeat
  • 6,501
  • 6
  • 57
  • 99
  • 2
    Simple and effective. Some users may prefer call_user_func_array to break the $args into individually passed variables instead of an array as the only parameter. Thanks Erwinus. – Dustin Graham Feb 23 '13 at 16:46
  • 2
    I know that you request a object of Closure, but why? You could also refer a class method which is not anonymous. Maybe you want to call a normal function/method too. – user3292653 Apr 30 '14 at 13:20
  • @user3292653 : If you do not agree, change it if you like, source is free ;-) – Codebeat Jun 14 '16 at 20:25
  • DO NOT USE STATIC. This code will be hard to mock or test in future. Even extension will be limited. (It also break SOLID principles) – John Tribe May 09 '18 at 13:55
  • @JohnTribe : Previously you changed the code (and it doesn't makes any sense at all) and now you shouting again. It is fine you don't agree, tip: Don't use it. – Codebeat May 09 '18 at 19:05
  • @Codebeat Sorry about shouting. I thought that You did not see my change. But You just .ignore it. Do not understand why ? Please explain. Your Idea is ok, but implementation.... – John Tribe May 10 '18 at 14:55
13

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.

user187291
  • 53,363
  • 19
  • 95
  • 127
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.

scoates
  • 853
  • 7
  • 11
  • -1 as this does not really answer the question at hand of HOW to create an event listener library. This should have been a comment. – Craige Dec 17 '10 at 14:28
  • 5
    +1 since Lithium is a great example of how to do events/listening. It's a little bit difficult to get your head around the first time, but it's extremely powerful: [A useful article about it](http://www.shift8creative.com/blog/aspect-oriented-programming-lithiums-filters), [Source](http://rad-dev.org/lithium/source/libraries/lithium/core/Object.php). – ircmaxell Dec 17 '10 at 14:39
  • Combining inspiration from Lithium with stream filters, token reflection, decorators, pointcuts and closures from PHP5.4 into one place results in the true AOP framework: [Go! AOP PHP](http://go.aopphp.com), [source](https://github.com/lisachenko/go-aop-php) – lisachenko Jun 19 '13 at 06:34