Almost every time the topic of events in PHP comes up, the responses I usually get tend to be in the direction of “PHP doesn’t have real events”, “PHP executes synchronously, how can it use events” etc. Although there are some truth to these statements, events doesn’t have to by asynchronous or non-blocking. Events are defined as “something that happens”. So if you are uncomfortable with calling them events, just call them “hooks”.

It’s quite easy to create the behaviour of events in PHP. There are some design patterns like the Observer pattern, which is as close as you need to get to events. The concept behind an Observer pattern is that you have a subject (publisher) that notifies subscribers when something occurs. The publisher has no idea what the subscriber is going to do with the data, all it knows is that the subscriber is sitting there waiting for a notification of when the specific scenario occurs.

 1 <?php
 2 
 3 // Interface for subscribers to listen for events
 4 interface SubscriberInterface
 5 {
 6     public function update(PublisherInterface $pub);
 7 }
 8 
 9 // Publishers are able to notify subscribers of events
10 interface PublisherInterface
11 {
12     public function notify();
13 }
14 
15 // Abstraction for a class that needs to implements the Observer pattern
16 abstract AbstractPublisher implements PublisherInterface
17 {
18     private $observers;
19 
20     public function notify()
21     {
22         foreach ($this->observers as $obs)
23         {
24             $obs->update($this);
25         }
26     }
27 
28     public function addSubscriber(SubscriberInterface $sub)
29     {
30         $this->observers[] = $sub;
31     }
32 }

This would be the typical interface and abstraction of what a publisher will look like. Very simple, it only needs to be able notify subscribers.

Let’s have a look at how the code sample below will notify it’s subscribers of when a blog is updated.

 1 <?php
 2 
 3 class BlogLogger implements SubscriberInterface
 4 {
 5     public function update(BlogPublisher $pub)
 6     {
 7         // Log to file
 8         // $pub->getTitle();
 9     }
10 }
11 
12 class BlogPublisher implements PublisherInterface
13 {
14     public function getTitle()
15     {
16         return 'Blog Title';
17     }
18 
19     public function updateBlog()
20     {
21         $this->notify();
22     }
23 }

The example above shows us just how the subscriber and publisher interact with each other. We can take this even further, we can take this pattern and manipulate it a little to accomplish some powerful event management.

In the observer pattern, the subject (publisher) is responsible for keeping track of it’s own subscribers. For small systems or specific use cases, this will work just fine…but when you have a large system, it gets easier to take the “management” part of the events and put it into it’s own class or component.

By doing this, the framework or systems will know that they need to notify the event manager of when they do something important and the manager will be aware of any subscribers (if any).

 1 <?php
 2 
 3 class EventManager
 4 {
 5     private $listeners = array();
 6 
 7     public function listen($event, $callback)
 8     {
 9         $this->listeners[$event][] = $callback;
10     }
11 
12     public function dispatch($event, PublisherInterface $param)
13     {
14         foreach ($this->listeners[$event] as $listener)
15         {
16             call_user_func_array($listener, array($param));
17         }
18     }
19 }
20 
21 class BlogPublisher implements PublisherInterface
22 {
23     private $event;
24 
25     public function __construct(EventManager $event)
26     {
27         $this->event = $event;
28     }
29 
30     public function getTitle()
31     {
32         return 'Blog Title';
33     }
34 
35     public function getDispatcher()
36     {
37         return $this->event;
38     }
39 
40     public function updateBlog()
41     {
42         $this->getDispatcher()->dispatch('blog_update', $this);
43     }
44 }
45 
46 // Create the manager
47 $event = new EventManager();
48 
49 // Create the blog updater
50 $blog = new BlogPublisher($event);
51 
52 // Register a subscriber, can be anything
53 $event->listen('blog_update', function(BlogPublisher $pub) {
54     // Write to log
55     // $pub->getTitle();
56 });
57 
58 // Update the blog
59 $blog->updateBlog();

By abstracting the event management into a component of it’s own, we can introduce error handling into that component to ensure that any errors that might occur from events won’t interrupt the main flow of the program. In addition, we can have different types of exceptions where one of them might actually be capable of interrupting the main flow if necessary (like an ACL component that needs to blog certain actions).

You will also notice, the need for a SubscriberInterface is now no longer necessary. Using a manager means that we can assign any “callable” as a listener to an event. With this implementation, your subscribers can “listen” to certain events in the manager. These events don’t have to exist. This allows for the development of loosely coupled systems that can easily be replaced or removed with minimal affect.

However, you might have scenario’s where you want a certain class to listen to multiple events. If we change the SubscriberInterface to expose a list of events it wishes to subscribe to…it centralizes the location in which a subscriber can define it’s interest. If we don’t do this, it will be necessary to register multiple listeners and manually keep track of where they are registered. With the subscriber listening to multiple events, one update function won’t do. Instead of creating elaborate if statements or switch cases in the update function, it’s more ideal to have each event or notification map to a specified function.

 1 <?php
 2 
 3 interface SubscriberInterface
 4 {
 5     public static function getSubscribedEvents();
 6 }
 7 
 8 class BlogSubscriber implements SubscriberInterface
 9 {
10     public static function getSubscribedEvents()
11     {
12         return array(
13             'blog_update' => 'Listener'
14         );
15     }
16 
17     // The subscribed function
18     public function Listener(BlogPublisher $pub)
19     {
20         // Write to log
21         // $pub->getTitle();
22     }
23 }
24 
25 // We also need to extend the event manager to
26 // deal with subscribed interfaces.
27 class EventManager
28 {
29     private $listeners = array();
30 
31     public function listen($event, $callback)
32     {
33         $this->listeners[$event][] = $callback;
34     }
35 
36     public function dispatch($event, PublisherInterface $param)
37     {
38         foreach ($this->listeners[$event] as $listener)
39         {
40             call_user_func_array($listener, array($param));
41         }
42     }
43 
44     public function addSubscriber(SubscriberInterface $sub)
45     {
46         $listeners = $sub->getSubscribedEvents();
47 
48         foreach ($listeners as $event => $listener)
49         {
50             // Add the subscribed function as an event
51             $this->listen($event, array($sub, $listener));
52         }
53     }
54 }
55 
56 // How it works
57 $event = new EventManager();
58 
59 // Create the blog updater
60 $blog = new BlogPublisher($event);
61 
62 // Register a subscriber, can be anything
63 $subscriber = new BlogSubscriber();
64 
65 // Add the subscriber
66 $event->addSubscriber($subscriber);
67 
68 // Update the blog
69 $blog->updateBlog();

As you can see, it’s not that difficult to have PHP implement an event based design pattern. Just because these events execute synchronously does not mean we cannot assign priorities to them, or even stop propagation. We also get the benefit of being able to override certain behaviour of the system in these events. The makes building a plugin system or extension manager much easier.

One last addition we can make, is to publish certain event objects that inherit from a base event. By doing this, we can ensure that the subscribers always know exactly “what” is being published to them. Doing so will mean the publisher can change without affecting it’s subscribers.

 1 <?php
 2 
 3 abstract class Event { }
 4 
 5 class BlogEvent extends Event
 6 {
 7     private $title;
 8 
 9     public function __construct($title)
10     {
11         $this->title = $title;
12     }
13 
14     public function getTitle()
15     {
16         return $this->title;
17     }
18 }
19 
20 // Now we need to change the manager and publisher to publish
21 // an event abstraction.
22 class EventManager
23 {
24     ...
25     // Pass the event as parameter
26     public function dispatch($event, Event $event)
27     {
28         foreach ($this->listeners[$event] as $listener)
29         {
30             call_user_func_array($listener, array($event));
31         }
32     }
33 }
34 
35 
36 class BlogPublisher implements PublisherInterface
37 {
38     ...
39 
40     public function updateBlog()
41     {
42         $event = new BlogEvent($this->getTitle());
43         // Include the event when published
44         $this->getDispatcher()->dispatch('blog_update', $event);
45     }
46 }
47 
48 // Our subscriber now changes to use the event object instead
49 class BlogSubscriber implements SubscriberInterface
50 {
51     ...
52     // The subscribed function
53     public function Listener(BlogEvent $blog_event)
54     {
55         // Write to log
56         // $blog_event->getTitle();
57     }
58 }

Now our subscriber is aware of exactly how it can interact with the published result.

There are quite a few open source event libraries out there which this article is based on. I encourage you to use one of these libraries instead of writing your own, but I hope this article gave you insight into how events can be handled in PHP. For more elaborate examples of what can be done with an event dispatcher, I would recommend you take a look at the Symfony Event Dispatcher component.