Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / lib / Drupal / Component / EventDispatcher / ContainerAwareEventDispatcher.php
1 <?php
2
3 namespace Drupal\Component\EventDispatcher;
4
5 use Symfony\Component\DependencyInjection\ContainerInterface;
6 use Symfony\Component\EventDispatcher\Event;
7 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
9
10 /**
11  * A performance optimized container aware event dispatcher.
12  *
13  * This version of the event dispatcher contains the following optimizations
14  * in comparison to the Symfony event dispatcher component:
15  *
16  * <dl>
17  *   <dt>Faster instantiation of the event dispatcher service</dt>
18  *   <dd>
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
23  *     pass.
24  *   </dd>
25  *   <dt>Lazy instantiation of listeners</dt>
26  *   <dd>
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.
31  *   </dd>
32  * </dl>
33  */
34 class ContainerAwareEventDispatcher implements EventDispatcherInterface {
35
36   /**
37    * The service container.
38    *
39    * @var \Symfony\Component\DependencyInjection\ContainerInterface;
40    */
41   protected $container;
42
43   /**
44    * Listener definitions.
45    *
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
48    * value pairs:
49    * - callable: A callable listener
50    * - service: An array of the form [service id, method]
51    *
52    * A service entry will be resolved to a callable only just before its
53    * invocation.
54    *
55    * @var array
56    */
57   protected $listeners;
58
59   /**
60    * Whether listeners need to be sorted prior to dispatch, keyed by event name.
61    *
62    * @var TRUE[]
63    */
64   protected $unsorted;
65
66   /**
67    * Constructs a container aware event dispatcher.
68    *
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
78    *   invocation.
79    */
80   public function __construct(ContainerInterface $container, array $listeners = []) {
81     $this->container = $container;
82     $this->listeners = $listeners;
83     $this->unsorted = [];
84   }
85
86   /**
87    * {@inheritdoc}
88    */
89   public function dispatch($event_name, Event $event = NULL) {
90     if ($event === NULL) {
91       $event = new Event();
92     }
93
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]);
99       }
100
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]];
106           }
107           if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
108             $definition['callable'][0] = $definition['callable'][0]();
109           }
110
111           call_user_func($definition['callable'], $event, $event_name, $this);
112           if ($event->isPropagationStopped()) {
113             return $event;
114           }
115         }
116       }
117     }
118
119     return $event;
120   }
121
122   /**
123    * {@inheritdoc}
124    */
125   public function getListeners($event_name = NULL) {
126     $result = [];
127
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;
134         }
135       }
136     }
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]);
142       }
143
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]];
149           }
150           if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
151             $definition['callable'][0] = $definition['callable'][0]();
152           }
153
154           $result[] = $definition['callable'];
155         }
156       }
157     }
158
159     return $result;
160   }
161
162   /**
163    * {@inheritdoc}
164    */
165   public function getListenerPriority($event_name, $listener) {
166     if (!isset($this->listeners[$event_name])) {
167       return;
168     }
169     if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
170       $listener[0] = $listener[0]();
171     }
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],
181           ];
182         }
183         if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
184           $definition['callable'][0] = $definition['callable'][0]();
185         }
186         if ($definition['callable'] === $listener) {
187           return $priority;
188         }
189       }
190     }
191   }
192
193   /**
194    * {@inheritdoc}
195    */
196   public function hasListeners($event_name = NULL) {
197     if ($event_name !== NULL) {
198       return !empty($this->listeners[$event_name]);
199     }
200
201     foreach ($this->listeners as $event_listeners) {
202       if ($event_listeners) {
203         return TRUE;
204       }
205     }
206
207     return FALSE;
208   }
209
210   /**
211    * {@inheritdoc}
212    */
213   public function addListener($event_name, $listener, $priority = 0) {
214     $this->listeners[$event_name][$priority][] = ['callable' => $listener];
215     $this->unsorted[$event_name] = TRUE;
216   }
217
218   /**
219    * {@inheritdoc}
220    */
221   public function removeListener($event_name, $listener) {
222     if (!isset($this->listeners[$event_name])) {
223       return;
224     }
225
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])) {
230             continue;
231           }
232           $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
233         }
234
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]();
237         }
238
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]();
241         }
242         if ($definition['callable'] === $listener) {
243           unset($definitions[$key]);
244         }
245       }
246       if ($definitions) {
247         $this->listeners[$event_name][$priority] = $definitions;
248       }
249       else {
250         unset($this->listeners[$event_name][$priority]);
251       }
252     }
253   }
254
255   /**
256    * {@inheritdoc}
257    */
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]);
262       }
263       elseif (is_string($params[0])) {
264         $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
265       }
266       else {
267         foreach ($params as $listener) {
268           $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
269         }
270       }
271     }
272   }
273
274   /**
275    * {@inheritdoc}
276    */
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]]);
282         }
283       }
284       else {
285         $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);
286       }
287     }
288   }
289
290 }