comparison core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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 }