Upgraded drupal core with security updates
[yaffs-website] / web / core / lib / Drupal / Component / EventDispatcher / ContainerAwareEventDispatcher.php
1 <?php
2
3 namespace Drupal\Component\EventDispatcher;
4
5 use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
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\IntrospectableContainerInterface;
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\IntrospectableContainerInterface $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(IntrospectableContainerInterface $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     $event->setDispatcher($this);
95     $event->setName($event_name);
96
97     if (isset($this->listeners[$event_name])) {
98       // Sort listeners if necessary.
99       if (isset($this->unsorted[$event_name])) {
100         krsort($this->listeners[$event_name]);
101         unset($this->unsorted[$event_name]);
102       }
103
104       // Invoke listeners and resolve callables if necessary.
105       foreach ($this->listeners[$event_name] as $priority => &$definitions) {
106         foreach ($definitions as $key => &$definition) {
107           if (!isset($definition['callable'])) {
108             $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
109           }
110
111           $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
151           $result[] = $definition['callable'];
152         }
153       }
154     }
155
156     return $result;
157   }
158
159   /**
160    * {@inheritdoc}
161    */
162   public function getListenerPriority($eventName, $listener) {
163     // Parts copied from \Symfony\Component\EventDispatcher, that's why you see
164     // a yoda condition here.
165     if (!isset($this->listeners[$eventName])) {
166       return;
167     }
168     foreach ($this->listeners[$eventName] as $priority => $listeners) {
169       if (FALSE !== ($key = array_search(['callable' => $listener], $listeners, TRUE))) {
170         return $priority;
171       }
172     }
173     // Resolve service definitions if the listener has not been found so far.
174     foreach ($this->listeners[$eventName] as $priority => &$definitions) {
175       foreach ($definitions as $key => &$definition) {
176         if (!isset($definition['callable'])) {
177           // Once the callable is retrieved we keep it for subsequent method
178           // invocations on this class.
179           $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
180           if ($definition['callable'] === $listener) {
181             return $priority;
182           }
183         }
184       }
185     }
186   }
187
188   /**
189    * {@inheritdoc}
190    */
191   public function hasListeners($event_name = NULL) {
192     return (bool) count($this->getListeners($event_name));
193   }
194
195   /**
196    * {@inheritdoc}
197    */
198   public function addListener($event_name, $listener, $priority = 0) {
199     $this->listeners[$event_name][$priority][] = ['callable' => $listener];
200     $this->unsorted[$event_name] = TRUE;
201   }
202
203   /**
204    * {@inheritdoc}
205    */
206   public function removeListener($event_name, $listener) {
207     if (!isset($this->listeners[$event_name])) {
208       return;
209     }
210
211     foreach ($this->listeners[$event_name] as $priority => $definitions) {
212       foreach ($definitions as $key => $definition) {
213         if (!isset($definition['callable'])) {
214           if (!$this->container->initialized($definition['service'][0])) {
215             continue;
216           }
217           $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
218         }
219
220         if ($definition['callable'] === $listener) {
221           unset($this->listeners[$event_name][$priority][$key]);
222         }
223       }
224     }
225   }
226
227   /**
228    * {@inheritdoc}
229    */
230   public function addSubscriber(EventSubscriberInterface $subscriber) {
231     foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
232       if (is_string($params)) {
233         $this->addListener($event_name, [$subscriber, $params]);
234       }
235       elseif (is_string($params[0])) {
236         $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
237       }
238       else {
239         foreach ($params as $listener) {
240           $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
241         }
242       }
243     }
244   }
245
246   /**
247    * {@inheritdoc}
248    */
249   public function removeSubscriber(EventSubscriberInterface $subscriber) {
250     foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
251       if (is_array($params) && is_array($params[0])) {
252         foreach ($params as $listener) {
253           $this->removeListener($event_name, [$subscriber, $listener[0]]);
254         }
255       }
256       else {
257         $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);
258       }
259     }
260   }
261
262 }