3 namespace Drupal\Component\EventDispatcher;
5 use Symfony\Component\DependencyInjection\ContainerInterface;
6 use Symfony\Component\EventDispatcher\Event;
7 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
11 * A performance optimized container aware event dispatcher.
13 * This version of the event dispatcher contains the following optimizations
14 * in comparison to the Symfony event dispatcher component:
17 * <dt>Faster instantiation of the event dispatcher service</dt>
19 * Instead of calling <code>addSubscriberService</code> once for each
20 * subscriber, a precompiled array of listener definitions is passed
21 * directly to the constructor. This is faster by roughly an order of
22 * magnitude. The listeners are collected and prepared using a compiler
25 * <dt>Lazy instantiation of listeners</dt>
27 * Services are only retrieved from the container just before invocation.
28 * Especially when dispatching the KernelEvents::REQUEST event, this leads
29 * to a more timely invocation of the first listener. Overall dispatch
30 * runtime is not affected by this change though.
34 class ContainerAwareEventDispatcher implements EventDispatcherInterface {
37 * The service container.
39 * @var \Symfony\Component\DependencyInjection\ContainerInterface;
44 * Listener definitions.
46 * A nested array of listener definitions keyed by event name and priority.
47 * A listener definition is an associative array with one of the following key
49 * - callable: A callable listener
50 * - service: An array of the form [service id, method]
52 * A service entry will be resolved to a callable only just before its
60 * Whether listeners need to be sorted prior to dispatch, keyed by event name.
67 * Constructs a container aware event dispatcher.
69 * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
70 * The service container.
71 * @param array $listeners
72 * A nested array of listener definitions keyed by event name and priority.
73 * The array is expected to be ordered by priority. A listener definition is
74 * an associative array with one of the following key value pairs:
75 * - callable: A callable listener
76 * - service: An array of the form [service id, method]
77 * A service entry will be resolved to a callable only just before its
80 public function __construct(ContainerInterface $container, array $listeners = []) {
81 $this->container = $container;
82 $this->listeners = $listeners;
89 public function dispatch($event_name, Event $event = NULL) {
90 if ($event === NULL) {
94 if (isset($this->listeners[$event_name])) {
95 // Sort listeners if necessary.
96 if (isset($this->unsorted[$event_name])) {
97 krsort($this->listeners[$event_name]);
98 unset($this->unsorted[$event_name]);
101 // Invoke listeners and resolve callables if necessary.
102 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
103 foreach ($definitions as $key => &$definition) {
104 if (!isset($definition['callable'])) {
105 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
107 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
108 $definition['callable'][0] = $definition['callable'][0]();
111 call_user_func($definition['callable'], $event, $event_name, $this);
112 if ($event->isPropagationStopped()) {
125 public function getListeners($event_name = NULL) {
128 if ($event_name === NULL) {
129 // If event name was omitted, collect all listeners of all events.
130 foreach (array_keys($this->listeners) as $event_name) {
131 $listeners = $this->getListeners($event_name);
132 if (!empty($listeners)) {
133 $result[$event_name] = $listeners;
137 elseif (isset($this->listeners[$event_name])) {
138 // Sort listeners if necessary.
139 if (isset($this->unsorted[$event_name])) {
140 krsort($this->listeners[$event_name]);
141 unset($this->unsorted[$event_name]);
144 // Collect listeners and resolve callables if necessary.
145 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
146 foreach ($definitions as $key => &$definition) {
147 if (!isset($definition['callable'])) {
148 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
150 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
151 $definition['callable'][0] = $definition['callable'][0]();
154 $result[] = $definition['callable'];
165 public function getListenerPriority($event_name, $listener) {
166 if (!isset($this->listeners[$event_name])) {
169 if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
170 $listener[0] = $listener[0]();
172 // Resolve service definitions if the listener has not been found so far.
173 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
174 foreach ($definitions as $key => &$definition) {
175 if (!isset($definition['callable'])) {
176 // Once the callable is retrieved we keep it for subsequent method
177 // invocations on this class.
178 $definition['callable'] = [
179 $this->container->get($definition['service'][0]),
180 $definition['service'][1],
183 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
184 $definition['callable'][0] = $definition['callable'][0]();
186 if ($definition['callable'] === $listener) {
196 public function hasListeners($event_name = NULL) {
197 if ($event_name !== NULL) {
198 return !empty($this->listeners[$event_name]);
201 foreach ($this->listeners as $event_listeners) {
202 if ($event_listeners) {
213 public function addListener($event_name, $listener, $priority = 0) {
214 $this->listeners[$event_name][$priority][] = ['callable' => $listener];
215 $this->unsorted[$event_name] = TRUE;
221 public function removeListener($event_name, $listener) {
222 if (!isset($this->listeners[$event_name])) {
226 foreach ($this->listeners[$event_name] as $priority => $definitions) {
227 foreach ($definitions as $key => $definition) {
228 if (!isset($definition['callable'])) {
229 if (!$this->container->initialized($definition['service'][0])) {
232 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
235 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure && !$listener instanceof \Closure) {
236 $definition['callable'][0] = $definition['callable'][0]();
239 if (is_array($definition['callable']) && isset($definition['callable'][0]) && !$definition['callable'][0] instanceof \Closure && is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
240 $listener[0] = $listener[0]();
242 if ($definition['callable'] === $listener) {
243 unset($definitions[$key]);
247 $this->listeners[$event_name][$priority] = $definitions;
250 unset($this->listeners[$event_name][$priority]);
258 public function addSubscriber(EventSubscriberInterface $subscriber) {
259 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
260 if (is_string($params)) {
261 $this->addListener($event_name, [$subscriber, $params]);
263 elseif (is_string($params[0])) {
264 $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
267 foreach ($params as $listener) {
268 $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
277 public function removeSubscriber(EventSubscriberInterface $subscriber) {
278 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
279 if (is_array($params) && is_array($params[0])) {
280 foreach ($params as $listener) {
281 $this->removeListener($event_name, [$subscriber, $listener[0]]);
285 $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);