Event Dispatcher
based on Symfony 2.8
Sommaire
- Problem
- Design Pattern
- How to use it
- Events
- Dispatcher
- Listeners
- Dig into Symfony
Problem
In order to have a good object oriented design we have to create lots of classes interacting one with each other. If certain principles are not applied the final framework will end in a total mess where each object relies on many other objects in order to run. In order to avoid tight coupled frameworks, we need a mechanism to facilitate the interaction between objects in a manner in that objects are not aware of the existence of other objects.
Source: oodesign.com/mediator-pattern.html
Design Pattern
Mediator Pattern
Communication between objects is encapsulated with a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby lowering the coupling.
Source: en.wikipedia.org/wiki/Mediator_pattern
Mediator
Without
With
Symfony EventDispatcher
{
"require": {
"symfony/event-dispatcher" : "^2.7"
}
}
$ composer require symfony/event-dispatcher
{
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7",
"symfony/dependency-injection": "~2.6",
"symfony/expression-language": "~2.6",
"symfony/config": "~2.0,>=2.0.5",
"symfony/stopwatch": "~2.3",
"psr/log": "~1.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
}
}
Install
1. Events
2. Dispatcher
3. Listeners
Event Names
have a unique name
kernel.request
kernel.response
form.bind
form.post_set_data
console.command
console.exception
Event Names
<?php
namespace Symfony\Component\HttpKernel;
final class KernelEvents
{
/**
* The REQUEST event occurs at the very beginning of request
* dispatching.
*
* This event allows you to create a response for a request before any
* other code in the framework is executed. The event listener method
* receives a Symfony\Component\HttpKernel\Event\GetResponseEvent
* instance.
*/
const REQUEST = 'kernel.request';
/**
* The EXCEPTION event occurs when an uncaught exception appears.
*
* This event allows you to create a response for a thrown exception or
* to modify the thrown exception. The event listener method receives
* a Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent
* instance.
*/
const EXCEPTION = 'kernel.exception';
// ...
}
Event Objects
Event |
---|
- propagationStopped: bool = false |
+ isPropagationStopped(): bool + stopPropagation() |
Instances of
Symfony\Component\EventDispatcher\Event
Event Objects
Symfony\Component\EventDispatcher\GenericEvent
GenericEvent |
---|
- subject : mixed - arguments : array |
+ getSubject(): mixed + getArgument(string): mixed + setArgument(string, mixed): mixed + getArguments(): mixed + setArguments(array): mixed + hasArgument(string): bool |
Event |
---|
\ArrayAccess |
---|
\IteratorAggregate |
---|
Dispatcher
Use a Single Instance
$dispatcher = $this->getContainer()->get('event_dispatcher');
Dispatcher
Symfony\Component\EventDispatcher\EventDispatcherInterface |
---|
+ dispatch(string, Event $event = null): Event + addListeners(string, callable, int = 0) + addSubscriber(EventSubscriberInterface) + removeListener(string, callable) + removeSubscriber(EventSubscriberInterface) + getListeners(string = null) + hasListeners(string = null) |
Dispatcher
$dispatcher = $this->getContainer()->get('event_dispatcher');
// event name only
$dispatcher->dispatch('store.order');
// with event object
$event = new StoreOrderEvent($order);
$dispatcher->dispatch(StoreEvents::STORE_ORDER, $event);
Dispatcher Extras
Symfony\Component\EventDispatcher\
ImmutableEventDispatcher
Symfony\Component\EventDispatcher\
ContainerAwareEventDispatcher
Symfony\Component\EventDispatcher\
Debug\TracableEventDispatcher
Listeners
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
$listener = function (Event $event) {
echo 'A';
};
$dispatcher->addListener(
'example', // Event name to listen
$listener, // A PHP callable (aka callback)
$priority = 0 // Priority
);
Reminder: PHP Callable
function my_callback_function() {
echo 'hello world!';
}
class MyClass {
static function myCallbackMethod() {
echo 'Hello World!';
}
}
// Type 1: Simple callback
call_user_func('my_callback_function');
// Type 2: Static class method call
call_user_func(array('MyClass', 'myCallbackMethod'));
// Type 3: Object method call
$obj = new MyClass();
call_user_func(array($obj, 'myCallbackMethod'));
// Type 4: Static class method call (As of PHP 5.2.3)
call_user_func('MyClass::myCallbackMethod');
Source: php.net/manual/en/language.types.callable.php
// Type 5: Relative static class method call (As of PHP 5.3.0)
class A {
public static function who() {
echo "A\n";
}
}
class B extends A {
public static function who() {
echo "B\n";
}
}
call_user_func(array('B', 'parent::who')); // A
// Type 6: Objects implementing __invoke can be used as callables (since PHP 5.3)
class C {
public function __invoke($name) {
echo 'Hello ', $name, "\n";
}
}
$c = new C();
call_user_func($c, 'PHP!');
// Anonymous functions
$listener = function (Event $e) {
echo "C\n";
}
$dispatcher->addListener('example', $listener);
Subscribers
Symfony\Component\EventDispatcher\EventSubscriberInterface |
---|
+ getSubscribedEvents(): array |
Subscribers
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class EventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'kernel.request' => 'onKernelRequest',
'user.update' => ['onUserUpdate', 5],
'blog_post.saved' => [
['updateRss', 10],
['sendEmails', 5],
]
];
}
public function onKernelRequest(GetResponseEvent $event) { /** ... */ }
public function onUserUpdate(GenericEvent $event) { /** ... */ }
public function updateRss(Event $event) { /** ... */ }
public function sendEmails(Event $event) { /** ... */ }
}
$dispatcher = new EventDispatcher();
$subscriber = new EventSubscriber();
$dispatcher->addSubscriber($subscriber);
Stop propagation
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
$dispatcher->addListener('example', function (Event $event) {
echo 'A';
$event->stopPropagation();
});
$dispatcher->addListener('example', function (Event $event) {
echo 'B';
});
$dispatcher->dispatch('example'); // output : A
Dig in Symfony
- Workflow
- List of events
- Service definition
- Tools
Workflow
List of Events
Kernel
Console
Form
Security
Doctrine
Kernel
Name | KernelEvents Constant | Event Object |
---|---|---|
kernel.request | KernelEvents::REQUEST | GetResponseEvent |
kernel.controller | KernelEvents::CONTROLLER | FilterControllerEvent |
kernel.view | KernelEvents::VIEW | GetResponseForControllerResultEvent |
kernel.response | KernelEvents::RESPONSE | FilterResponseEvent |
kernel.finish_request | KernelEvents::FINISH_REQUEST | FinishRequestEvent |
kernel.terminate | KernelEvents::TERMINATE | PostResponseEvent |
kernel.exception | KernelEvents::EXCEPTION | GetResponseForExceptionEvent |
Console
Name | ConsoleEvents Constant | Event Object |
---|---|---|
console.command | ConsoleEvents::COMMAND | ConsoleCommandEvent |
console.terminate | ConsoleEvents::TERMINATE | ConsoleTerminateEvent |
console.exception | ConsoleEvents::EXCEPTION | ConsoleExceptionEvent |
Form
Name | FormEvents Constant | Event Object |
---|---|---|
form.pre_bind | FormEvents::PRE_SUBMIT | FormEvent |
form.bind | FormEvents::SUBMIT | FormEvent |
form.post_bind | FormEvents::POST_SUBMIT | FormEvent |
form.pre_set_data | FormEvents::PRE_SET_DATA | FormEvent |
form.post_set_data | FormEvents::POST_SET_DATA | FormEvent |
Security
Name | SecurityEvents Constant | Event Object |
---|---|---|
security.interactive_login | SecurityEvents::INTERACTIVE_LOGIN | InteractiveLoginEvent |
security.switch_user | SecurityEvents::SWITCH_USER | SwitchUserEvent |
Name | AuthenticationEvents Constant | Event Object |
---|---|---|
security.authentication.success | AuthenticationEvents::AUTHENTICATION_SUCCESS | AuthenticationEvent |
security.authentication.failure | AuthenticationEvents::AUTHENTICATION_FAILURE | AuthenticationFailureEvent |
Doctrine
Name | Events Constant | Event Object |
---|---|---|
preRemove | Events::preRemove | LifecycleEventArgs |
postRemove | Events::postRemove | LifecycleEventArgs |
prePersist | Events::prePersist | LifecycleEventArgs |
postPersist | Events::postPersist | LifecycleEventArgs |
preUpdate | Events::preUpdate | PreUpdateEventArgs |
postUpdate | Events::postUpdate | LifecycleEventArgs |
postLoad | Events::postLoad | LifecycleEventArgs |
loadClassMetadata | Events::loadClassMetadata | LifecycleEventArgs |
preFlush | Events::preFlush | PreFlushEventArgs |
onFlush | Events::onFlush | LifecycleEventArgs |
postFlush | Events::postFlush | LifecycleEventArgs |
onClear | Events::onClear | LifecycleEventArgs |
Service definition
services:
app.store_manager:
class: AppBundle\Manager\Store
arguments:
- @event_dispatcher
app.user_event_listener:
class: AppBundle\EventListener\UserEventListener
tags:
- { name: kernel.event_listener,
event: user.login,
method: onLogin }
- { name: kernel.event_listener,
event: user.update,
method: onUpdate,
priority: -1 }
app.store_event_listener:
class: AppBundle\EventListener\StoreListener
tags:
- { name: kernel.event_subscriber }
Tags:
kernel.event_listener kernel.event_subscriber
Service:
event_dispatcher
/!\ Doctrine /!\
doctrine:
dbal:
default_connection: default
connections:
default:
driver: pdo_sqlite
memory: true
services:
my.listener:
class: Acme\SearchBundle\EventListener\SearchIndexer
tags:
- { name: doctrine.event_listener, event: postPersist }
my.listener2:
class: Acme\SearchBundle\EventListener\SearchIndexer2
tags:
- { name: doctrine.event_listener, event: postPersist, connection: default }
my.subscriber:
class: Acme\SearchBundle\EventListener\SearchIndexerSubscriber
tags:
- { name: doctrine.event_subscriber, connection: default }
Tags:
doctrine.event_listener doctrine.event_subscriber
Tools
Commands
$ app/console debug:event-dispatcher -h
Usage:
debug:event-dispatcher [options] [--] [<event>]
Arguments:
event An event name
Options:
--format=FORMAT The output format (txt, xml, json, or md) [default: "txt"]
--raw To output raw description
...
Help:
The debug:event-dispatcher command displays all configured listeners:
php app/console debug:event-dispatcher
To get specific listeners for an event, specify its name:
php app/console debug:event-dispatcher kernel.request
$ app/console debug:event-dispatcher
[event_dispatcher] Registered listeners by event
[Event] console.command
+-------+-------------------------------------------------------------------------------+
| Order | Callable |
+-------+-------------------------------------------------------------------------------+
| #1 | Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure() |
| #2 | Symfony\Bridge\Monolog\Handler\ConsoleHandler::onCommand() |
| #3 | Symfony\Bridge\Monolog\Handler\ConsoleHandler::onCommand() |
+-------+-------------------------------------------------------------------------------+
[Event] console.terminate
+-------+-----------------------------------------------------------------------------------+
| Order | Callable |
+-------+-----------------------------------------------------------------------------------+
| #1 | Symfony\Bundle\SwiftmailerBundle\EventListener\EmailSenderListener::onTerminate() |
| #2 | Symfony\Bridge\Monolog\Handler\ConsoleHandler::onTerminate() |
| #3 | Symfony\Bridge\Monolog\Handler\ConsoleHandler::onTerminate() |
+-------+-----------------------------------------------------------------------------------+
[Event] kernel.controller
+-------+-----------------------------------------------------------------------------------------------+
| Order | Callable |
+-------+-----------------------------------------------------------------------------------------------+
| #1 | Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector::onKernelController() |
| #2 | Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::onKernelController() |
| #3 | Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener::onKernelController() |
| #4 | Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener::onKernelController() |
| #5 | Sensio\Bundle\FrameworkExtraBundle\EventListener\HttpCacheListener::onKernelController() |
| #6 | Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener::onKernelController() |
| #7 | Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener::onKernelController() |
+-------+-----------------------------------------------------------------------------------------------+
[Event] kernel.exception
+-------+-----------------------------------------------------------------------------------+
| Order | Callable |
+-------+-----------------------------------------------------------------------------------+
| #1 | Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelException() |
| #2 | Symfony\Component\HttpKernel\EventListener\ExceptionListener::onKernelException() |
+-------+-----------------------------------------------------------------------------------+
[Event] kernel.finish_request
+-------+----------------------------------------------------------------------------------------+
| Order | Callable |
+-------+----------------------------------------------------------------------------------------+
| #1 | Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelFinishRequest() |
| #2 | Symfony\Component\HttpKernel\EventListener\TranslatorListener::onKernelFinishRequest() |
| #3 | Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelFinishRequest() |
| #4 | Symfony\Component\Security\Http\Firewall::onKernelFinishRequest() |
+-------+----------------------------------------------------------------------------------------+
Symfony Profiler
What next ?
https://github.com/symfony/symfony/pull/10823
Must read/watch
Source code | https://github.com/symfony/EventDispatcher |
Documentation | http://symfony.com/doc/current/components/event_dispatcher |
How Kris Builds Symfony Apps | https://youtu.be/W8MOIOyPbmM |
EventDispatcher in C | https://github.com/symfony/symfony/pull/10823 |
Questions
?
Symfony Event Dispatcher
By MyKiwi
Symfony Event Dispatcher
- 531