Today, we're going to learn how to use the Symfony event dispatcher component, which allows you to create events and listeners in your PHP applications. Thus, different components of your application can talk to each other with loosely coupled code.
What Is the Symfony Event Dispatcher Component?
You may be familiar with the event-observer pattern, which allows you to define listeners for system-generated events so that they are executed when the event is triggered. Similarly, the Symfony EventDispatcher component allows you to set up a system in which you could create custom events and listeners. In that way, you allow components in your application to react if something happens in a system.
In fact, the event dispatcher component provides three elements that you could build your app architecture around: event, listener, and dispatcher. The whole system is orchestrated by the dispatcher class, which raises events at appropriate points in an application and calls listeners associated with those events.
Let's assume that you want to allow other components in your application to react when the cache is cleared. In that case, you need to define the clear cache event in the first place. After the cache is cleared, you can use the dispatcher to raise the clear cache event, and that notifies all listeners that are listening to this event. This gives listeners the opportunity to purge component-specific caches.
In this article, we'll explore the basics of the event dispatcher component. We'll start with installation and configuration, and we'll also create a few real-world examples to demonstrate all the concepts mentioned above.
Installing and Configuring the Event Dispatcher
In this section, we're going to install the event dispatcher component. I assume that you've already installed Composer on your system, because we'll need it to install the EventDispatcher component.
Once you've installed Composer, go ahead and install the EventDispatcher component using the following command.
1 |
$composer require symfony/event-dispatcher
|
That should have created the composer.json file, which should look like this:
1 |
{
|
2 |
"require": { |
3 |
"symfony/event-dispatcher": "^5.4" |
4 |
}
|
5 |
}
|
Let's further edit the composer.json file to look like the following:
1 |
{
|
2 |
"require": { |
3 |
"symfony/event-dispatcher": "^5.4" |
4 |
},
|
5 |
"autoload": { |
6 |
"psr-4": { |
7 |
"EventDispatchers\\": "src" |
8 |
},
|
9 |
"classmap": ["src"] |
10 |
}
|
11 |
}
|
As we've added a new classmap entry, go ahead and update the Composer autoloader by running the following command.
1 |
$composer dump -o |
Now, you can use the EventDispatchers
namespace to autoload classes under the src directory.
So that's the installation part, but how are you supposed to use it? In fact, it's just a matter of including the autoload.php file created by Composer in your application, as shown in the following snippet.
1 |
<?php
|
2 |
require_once './vendor/autoload.php'; |
3 |
|
4 |
// application code
|
5 |
?>
|
How to Create, Dispatch, and Listen to Events
In this section, we'll go through an example which demonstrates how you could create a custom event and set up a listener for that event.
The Event Class
To start with, go ahead and create the src/Events/DemoEvent.php file with the following contents.
1 |
<?php
|
2 |
namespace EventDispatchers\Events; |
3 |
|
4 |
use Symfony\Contracts\EventDispatcher\Event; |
5 |
|
6 |
class DemoEvent extends Event |
7 |
{
|
8 |
const NAME = 'demo.event'; |
9 |
|
10 |
protected $foo; |
11 |
|
12 |
public function __construct() |
13 |
{
|
14 |
$this->foo = 'bar'; |
15 |
}
|
16 |
|
17 |
public function getFoo() |
18 |
{
|
19 |
return $this->foo; |
20 |
}
|
21 |
}
|
Our custom DemoEvent
class extends the core Event
class of the EventDispatcher component. The NAME
constant holds the name of our custom event—demo.event
. It's used when you want to set up a listener for this event.
The Listener Class
Next, let's create the listener class src/Listeners/DemoListener.php with the following contents.
1 |
<?php
|
2 |
namespace EventDispatchers\Listeners; |
3 |
|
4 |
use Symfony\Contracts\EventDispatcher\Event; |
5 |
|
6 |
class DemoListener |
7 |
{
|
8 |
public function onDemoEvent(Event $event) |
9 |
{
|
10 |
// fetch event information here
|
11 |
echo "DemoListener is called!\n"; |
12 |
echo "The value of the foo is: ".$event->getFoo()."\n"; |
13 |
}
|
14 |
}
|
The DemoListener
class implements the onDemoEvent
method which is triggered when the system dispatches the DemoEvent
event. Of course, it won't happen automatically yet, as we need to register the DemoListener
listener to listen to the demo.event
event using the EventDispatcher
class.
So far, we've created event and listener classes. Next, we'll see how to tie all these pieces together.
An Example File
Let's create the basic_example.php file with the following contents.
1 |
<?php
|
2 |
// basic_example.php
|
3 |
|
4 |
require_once './vendor/autoload.php'; |
5 |
use Symfony\Component\EventDispatcher\EventDispatcher; |
6 |
use EventDispatchers\Events\DemoEvent; |
7 |
use EventDispatchers\Listeners\DemoListener; |
8 |
|
9 |
// init event dispatcher
|
10 |
$dispatcher = new EventDispatcher(); |
11 |
|
12 |
// register listener for the 'demo.event' event
|
13 |
$listener = new DemoListener(); |
14 |
$dispatcher->addListener('demo.event', array($listener, 'onDemoEvent')); |
15 |
|
16 |
// dispatch
|
17 |
$dispatcher->dispatch(new DemoEvent(), DemoEvent::NAME); |
The EventDispatcher
class is the most important element in the EventDispatcher component—it allows you to bind listeners to events they want to listen to. We've used the addListener
method of the EventDispatcher
class to listen to the demo.event
event.
The first argument of the addListener
method is the PHP callable which is triggered when the registered event is dispatched, and the second argument is an event name. In our case, we've provided the DemoListener
object as a listener along with the onDemoEvent
method.
1 |
$dispatcher->addListener('demo.event', array($listener, 'onDemoEvent')); |
Finally, we've used the dispatch
method of the EventDispatcher
class to dispatch the demo.event
event.
1 |
$dispatcher->dispatch(DemoEvent::NAME, new DemoEvent()); |
When you run the basic_example.php file, it should produce the following output.
1 |
$php basic_example.php
|
2 |
DemoListener is called! |
3 |
The value of the foo is: bar |
As expected, the onDemoEvent
method of the DemoListener
class is called, and that in turn calls the getFoo
method of the DemoEvent
class to fetch the event-related information.
What Is an Event Subscriber?
In the previous section, we built an example which demonstrated how to create a custom event and a custom listener. We also discussed how to bind a listener to the specific event using the EventDispatcher
class.
That was a simple example, as we only wanted to set up a listener for a single event. On the other hand, if you want to set up listeners for multiple events or you want to logically group event handling logic in a single class, you should consider using event subscribers because they allow you to keep everything in one place.
In this section, we'll revise the example which was created in the previous section.
The Subscriber Class
The first thing that we need to do is to create a subscriber class which implements the EventSubscriberInterface
interface. Go ahead and create the src/Subsribers/DemoSubscriber.php class as shown in the following snippet.
1 |
<?php
|
2 |
namespace EventDispatchers\Subscribers; |
3 |
|
4 |
use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
5 |
use EventDispatchers\Events\DemoEvent; |
6 |
|
7 |
class DemoSubscriber implements EventSubscriberInterface |
8 |
{
|
9 |
public static function getSubscribedEvents() |
10 |
{
|
11 |
return array( |
12 |
DemoEvent::NAME => 'onDemoEvent', |
13 |
);
|
14 |
}
|
15 |
|
16 |
public function onDemoEvent(DemoEvent $event) |
17 |
{
|
18 |
// fetch event information here
|
19 |
echo "DemoListener is called!\n"; |
20 |
echo "The value of the foo is :".$event->getFoo()."\n"; |
21 |
}
|
22 |
}
|
Since the class DemoSubscriber
implements the EventSubscriberInterface
interface, it must implement the getSubscribedEvents
method. The getSubscribedEvents
method should return an array of events that you want to subscribe to. You need to provide the event name in an array key and the method name in an array value which is called when the event is triggered.
The last thing is to implement the listener method in the same class. In our case, we need to implement the onDemoEvent
method, and we've already done that.
An Example File
It's time to test our subscriber! Let's quickly create the subscriber_example.php file with the following contents.
1 |
<?php
|
2 |
require_once './vendor/autoload.php'; |
3 |
use Symfony\Component\EventDispatcher\EventDispatcher; |
4 |
use EventDispatchers\Subscribers\DemoSubscriber as DemoSubscriber; |
5 |
use EventDispatchers\Events\DemoEvent; |
6 |
|
7 |
// init event dispatcher
|
8 |
$dispatcher = new EventDispatcher(); |
9 |
|
10 |
// register subscriber
|
11 |
$subscriber = new DemoSubscriber(); |
12 |
$dispatcher->addSubscriber($subscriber); |
13 |
|
14 |
// dispatch
|
15 |
$dispatcher->dispatch(new DemoEvent(), DemoEvent::NAME); |
You need to use the addSubscriber
method of the EventDispatcher
class to subscribe your custom subscriber, and the EventDispatcher
class handles the rest. It fetches events to be subscribed from the getSubscribedEvents
method and sets up listeners for those events. Apart from that, everything is the same, and it should work as expected with no surprises.
Let's test it!
1 |
$php subscriber_example.php
|
2 |
DemoListener is called! |
3 |
The value of the foo is: bar |
And that was an event subscriber at your disposal! That also brings us to the end of this article.
How to Stop Event Propagation
Sometimes, multiple listeners are listening to the same event. In that case, you might want to stop event propagation to the next follow-up listeners. In this section, we'll see how you could achieve it with the help of a listener.
Let's revisit the listener class which we created earlier.
1 |
<?php
|
2 |
namespace EventDispatchers\Listeners; |
3 |
|
4 |
use Symfony\Contracts\EventDispatcher\Event; |
5 |
|
6 |
class DemoListener |
7 |
{
|
8 |
public function onDemoEvent(Event $event) |
9 |
{
|
10 |
// fetch event information here
|
11 |
echo "DemoListener is called!\n"; |
12 |
echo "The value of the foo is: ".$event->getFoo()."\n"; |
13 |
$event->stopPropagation(); |
14 |
}
|
15 |
}
|
As you can see, we've used the stopPropagation
method to stop event propagation. It means that listeners that are listening to the demo.event
event and are not yet called, won't be called. So this is the last listener which is called for this event. You can also use the isPropagationStopped
method to detect if the event propagation was stopped by any listener.
How to Use PHP Closures as Listeners
So far, we've discussed how to add PHP objects as listeners. In fact, you can also use PHP closures instead of PHP objects.
Let's quickly revise the earlier example.
1 |
<?php
|
2 |
require_once './vendor/autoload.php'; |
3 |
use Symfony\Component\EventDispatcher\EventDispatcher; |
4 |
use EventDispatchers\Events\DemoEvent; |
5 |
|
6 |
// init event dispatcher
|
7 |
$dispatcher = new EventDispatcher(); |
8 |
|
9 |
// register closure listener for the 'demo.event' event
|
10 |
$dispatcher->addListener('demo.event', function (DemoEvent $event) { |
11 |
echo "DemoListener is called!\n"; |
12 |
echo "The value of the foo is: ".$event->getFoo()."\n"; |
13 |
});
|
14 |
|
15 |
// dispatch
|
16 |
$dispatcher->dispatch(new DemoEvent(), 'demo.event'); |
As you can see, we've used the PHP closure in the second argument of the addListener
method.
Conclusion
Today, we explored the Symfony event dispatcher component, which allows you to set up events and listeners in your PHP applications. By using this library, you can create a loosely coupled system which allows components of your application to communicate with each other effortlessly.