Security update for Core, with self-updated composer
[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
108           $definition['callable']($event, $event_name, $this);
109           if ($event->isPropagationStopped()) {
110             return $event;
111           }
112         }
113       }
114     }
115
116     return $event;
117   }
118
119   /**
120    * {@inheritdoc}
121    */
122   public function getListeners($event_name = NULL) {
123     $result = [];
124
125     if ($event_name === NULL) {
126       // If event name was omitted, collect all listeners of all events.
127       foreach (array_keys($this->listeners) as $event_name) {
128         $listeners = $this->getListeners($event_name);
129         if (!empty($listeners)) {
130           $result[$event_name] = $listeners;
131         }
132       }
133     }
134     elseif (isset($this->listeners[$event_name])) {
135       // Sort listeners if necessary.
136       if (isset($this->unsorted[$event_name])) {
137         krsort($this->listeners[$event_name]);
138         unset($this->unsorted[$event_name]);
139       }
140
141       // Collect listeners and resolve callables if necessary.
142       foreach ($this->listeners[$event_name] as $priority => &$definitions) {
143         foreach ($definitions as $key => &$definition) {
144           if (!isset($definition['callable'])) {
145             $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
146           }
147
148           $result[] = $definition['callable'];
149         }
150       }
151     }
152
153     return $result;
154   }
155
156   /**
157    * {@inheritdoc}
158    */
159   public function getListenerPriority($eventName, $listener) {
160     // Parts copied from \Symfony\Component\EventDispatcher, that's why you see
161     // a yoda condition here.
162     if (!isset($this->listeners[$eventName])) {
163       return;
164     }
165     foreach ($this->listeners[$eventName] as $priority => $listeners) {
166       if (FALSE !== ($key = array_search(['callable' => $listener], $listeners, TRUE))) {
167         return $priority;
168       }
169     }
170     // Resolve service definitions if the listener has not been found so far.
171     foreach ($this->listeners[$eventName] as $priority => &$definitions) {
172       foreach ($definitions as $key => &$definition) {
173         if (!isset($definition['callable'])) {
174           // Once the callable is retrieved we keep it for subsequent method
175           // invocations on this class.
176           $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
177           if ($definition['callable'] === $listener) {
178             return $priority;
179           }
180         }
181       }
182     }
183   }
184
185   /**
186    * {@inheritdoc}
187    */
188   public function hasListeners($event_name = NULL) {
189     return (bool) count($this->getListeners($event_name));
190   }
191
192   /**
193    * {@inheritdoc}
194    */
195   public function addListener($event_name, $listener, $priority = 0) {
196     $this->listeners[$event_name][$priority][] = ['callable' => $listener];
197     $this->unsorted[$event_name] = TRUE;
198   }
199
200   /**
201    * {@inheritdoc}
202    */
203   public function removeListener($event_name, $listener) {
204     if (!isset($this->listeners[$event_name])) {
205       return;
206     }
207
208     foreach ($this->listeners[$event_name] as $priority => $definitions) {
209       foreach ($definitions as $key => $definition) {
210         if (!isset($definition['callable'])) {
211           if (!$this->container->initialized($definition['service'][0])) {
212             continue;
213           }
214           $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
215         }
216
217         if ($definition['callable'] === $listener) {
218           unset($this->listeners[$event_name][$priority][$key]);
219         }
220       }
221     }
222   }
223
224   /**
225    * {@inheritdoc}
226    */
227   public function addSubscriber(EventSubscriberInterface $subscriber) {
228     foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
229       if (is_string($params)) {
230         $this->addListener($event_name, [$subscriber, $params]);
231       }
232       elseif (is_string($params[0])) {
233         $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
234       }
235       else {
236         foreach ($params as $listener) {
237           $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
238         }
239       }
240     }
241   }
242
243   /**
244    * {@inheritdoc}
245    */
246   public function removeSubscriber(EventSubscriberInterface $subscriber) {
247     foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
248       if (is_array($params) && is_array($params[0])) {
249         foreach ($params as $listener) {
250           $this->removeListener($event_name, [$subscriber, $listener[0]]);
251         }
252       }
253       else {
254         $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);
255       }
256     }
257   }
258
259 }