Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Component\EventDispatcher;
|
Chris@0
|
4
|
Chris@0
|
5 use Symfony\Component\DependencyInjection\ContainerInterface;
|
Chris@0
|
6 use Symfony\Component\EventDispatcher\Event;
|
Chris@0
|
7 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
Chris@0
|
8 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
Chris@0
|
9
|
Chris@0
|
10 /**
|
Chris@0
|
11 * A performance optimized container aware event dispatcher.
|
Chris@0
|
12 *
|
Chris@0
|
13 * This version of the event dispatcher contains the following optimizations
|
Chris@0
|
14 * in comparison to the Symfony event dispatcher component:
|
Chris@0
|
15 *
|
Chris@0
|
16 * <dl>
|
Chris@0
|
17 * <dt>Faster instantiation of the event dispatcher service</dt>
|
Chris@0
|
18 * <dd>
|
Chris@0
|
19 * Instead of calling <code>addSubscriberService</code> once for each
|
Chris@0
|
20 * subscriber, a precompiled array of listener definitions is passed
|
Chris@0
|
21 * directly to the constructor. This is faster by roughly an order of
|
Chris@0
|
22 * magnitude. The listeners are collected and prepared using a compiler
|
Chris@0
|
23 * pass.
|
Chris@0
|
24 * </dd>
|
Chris@0
|
25 * <dt>Lazy instantiation of listeners</dt>
|
Chris@0
|
26 * <dd>
|
Chris@0
|
27 * Services are only retrieved from the container just before invocation.
|
Chris@0
|
28 * Especially when dispatching the KernelEvents::REQUEST event, this leads
|
Chris@0
|
29 * to a more timely invocation of the first listener. Overall dispatch
|
Chris@0
|
30 * runtime is not affected by this change though.
|
Chris@0
|
31 * </dd>
|
Chris@0
|
32 * </dl>
|
Chris@0
|
33 */
|
Chris@0
|
34 class ContainerAwareEventDispatcher implements EventDispatcherInterface {
|
Chris@0
|
35
|
Chris@0
|
36 /**
|
Chris@0
|
37 * The service container.
|
Chris@0
|
38 *
|
Chris@17
|
39 * @var \Symfony\Component\DependencyInjection\ContainerInterface
|
Chris@0
|
40 */
|
Chris@0
|
41 protected $container;
|
Chris@0
|
42
|
Chris@0
|
43 /**
|
Chris@0
|
44 * Listener definitions.
|
Chris@0
|
45 *
|
Chris@0
|
46 * A nested array of listener definitions keyed by event name and priority.
|
Chris@0
|
47 * A listener definition is an associative array with one of the following key
|
Chris@0
|
48 * value pairs:
|
Chris@0
|
49 * - callable: A callable listener
|
Chris@0
|
50 * - service: An array of the form [service id, method]
|
Chris@0
|
51 *
|
Chris@0
|
52 * A service entry will be resolved to a callable only just before its
|
Chris@0
|
53 * invocation.
|
Chris@0
|
54 *
|
Chris@0
|
55 * @var array
|
Chris@0
|
56 */
|
Chris@0
|
57 protected $listeners;
|
Chris@0
|
58
|
Chris@0
|
59 /**
|
Chris@0
|
60 * Whether listeners need to be sorted prior to dispatch, keyed by event name.
|
Chris@0
|
61 *
|
Chris@0
|
62 * @var TRUE[]
|
Chris@0
|
63 */
|
Chris@0
|
64 protected $unsorted;
|
Chris@0
|
65
|
Chris@0
|
66 /**
|
Chris@0
|
67 * Constructs a container aware event dispatcher.
|
Chris@0
|
68 *
|
Chris@0
|
69 * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
Chris@0
|
70 * The service container.
|
Chris@0
|
71 * @param array $listeners
|
Chris@0
|
72 * A nested array of listener definitions keyed by event name and priority.
|
Chris@0
|
73 * The array is expected to be ordered by priority. A listener definition is
|
Chris@0
|
74 * an associative array with one of the following key value pairs:
|
Chris@0
|
75 * - callable: A callable listener
|
Chris@0
|
76 * - service: An array of the form [service id, method]
|
Chris@0
|
77 * A service entry will be resolved to a callable only just before its
|
Chris@0
|
78 * invocation.
|
Chris@0
|
79 */
|
Chris@0
|
80 public function __construct(ContainerInterface $container, array $listeners = []) {
|
Chris@0
|
81 $this->container = $container;
|
Chris@0
|
82 $this->listeners = $listeners;
|
Chris@0
|
83 $this->unsorted = [];
|
Chris@0
|
84 }
|
Chris@0
|
85
|
Chris@0
|
86 /**
|
Chris@0
|
87 * {@inheritdoc}
|
Chris@0
|
88 */
|
Chris@0
|
89 public function dispatch($event_name, Event $event = NULL) {
|
Chris@0
|
90 if ($event === NULL) {
|
Chris@0
|
91 $event = new Event();
|
Chris@0
|
92 }
|
Chris@0
|
93
|
Chris@0
|
94 if (isset($this->listeners[$event_name])) {
|
Chris@0
|
95 // Sort listeners if necessary.
|
Chris@0
|
96 if (isset($this->unsorted[$event_name])) {
|
Chris@0
|
97 krsort($this->listeners[$event_name]);
|
Chris@0
|
98 unset($this->unsorted[$event_name]);
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 // Invoke listeners and resolve callables if necessary.
|
Chris@0
|
102 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
|
Chris@0
|
103 foreach ($definitions as $key => &$definition) {
|
Chris@0
|
104 if (!isset($definition['callable'])) {
|
Chris@0
|
105 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
|
Chris@0
|
106 }
|
Chris@14
|
107 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
|
Chris@14
|
108 $definition['callable'][0] = $definition['callable'][0]();
|
Chris@14
|
109 }
|
Chris@0
|
110
|
Chris@14
|
111 call_user_func($definition['callable'], $event, $event_name, $this);
|
Chris@0
|
112 if ($event->isPropagationStopped()) {
|
Chris@0
|
113 return $event;
|
Chris@0
|
114 }
|
Chris@0
|
115 }
|
Chris@0
|
116 }
|
Chris@0
|
117 }
|
Chris@0
|
118
|
Chris@0
|
119 return $event;
|
Chris@0
|
120 }
|
Chris@0
|
121
|
Chris@0
|
122 /**
|
Chris@0
|
123 * {@inheritdoc}
|
Chris@0
|
124 */
|
Chris@0
|
125 public function getListeners($event_name = NULL) {
|
Chris@0
|
126 $result = [];
|
Chris@0
|
127
|
Chris@0
|
128 if ($event_name === NULL) {
|
Chris@0
|
129 // If event name was omitted, collect all listeners of all events.
|
Chris@0
|
130 foreach (array_keys($this->listeners) as $event_name) {
|
Chris@0
|
131 $listeners = $this->getListeners($event_name);
|
Chris@0
|
132 if (!empty($listeners)) {
|
Chris@0
|
133 $result[$event_name] = $listeners;
|
Chris@0
|
134 }
|
Chris@0
|
135 }
|
Chris@0
|
136 }
|
Chris@0
|
137 elseif (isset($this->listeners[$event_name])) {
|
Chris@0
|
138 // Sort listeners if necessary.
|
Chris@0
|
139 if (isset($this->unsorted[$event_name])) {
|
Chris@0
|
140 krsort($this->listeners[$event_name]);
|
Chris@0
|
141 unset($this->unsorted[$event_name]);
|
Chris@0
|
142 }
|
Chris@0
|
143
|
Chris@0
|
144 // Collect listeners and resolve callables if necessary.
|
Chris@0
|
145 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
|
Chris@0
|
146 foreach ($definitions as $key => &$definition) {
|
Chris@0
|
147 if (!isset($definition['callable'])) {
|
Chris@0
|
148 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
|
Chris@0
|
149 }
|
Chris@14
|
150 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
|
Chris@14
|
151 $definition['callable'][0] = $definition['callable'][0]();
|
Chris@14
|
152 }
|
Chris@0
|
153
|
Chris@0
|
154 $result[] = $definition['callable'];
|
Chris@0
|
155 }
|
Chris@0
|
156 }
|
Chris@0
|
157 }
|
Chris@0
|
158
|
Chris@0
|
159 return $result;
|
Chris@0
|
160 }
|
Chris@0
|
161
|
Chris@0
|
162 /**
|
Chris@0
|
163 * {@inheritdoc}
|
Chris@0
|
164 */
|
Chris@14
|
165 public function getListenerPriority($event_name, $listener) {
|
Chris@14
|
166 if (!isset($this->listeners[$event_name])) {
|
Chris@0
|
167 return;
|
Chris@0
|
168 }
|
Chris@14
|
169 if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
|
Chris@14
|
170 $listener[0] = $listener[0]();
|
Chris@0
|
171 }
|
Chris@0
|
172 // Resolve service definitions if the listener has not been found so far.
|
Chris@14
|
173 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
|
Chris@0
|
174 foreach ($definitions as $key => &$definition) {
|
Chris@0
|
175 if (!isset($definition['callable'])) {
|
Chris@0
|
176 // Once the callable is retrieved we keep it for subsequent method
|
Chris@0
|
177 // invocations on this class.
|
Chris@14
|
178 $definition['callable'] = [
|
Chris@14
|
179 $this->container->get($definition['service'][0]),
|
Chris@14
|
180 $definition['service'][1],
|
Chris@14
|
181 ];
|
Chris@14
|
182 }
|
Chris@14
|
183 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
|
Chris@14
|
184 $definition['callable'][0] = $definition['callable'][0]();
|
Chris@14
|
185 }
|
Chris@14
|
186 if ($definition['callable'] === $listener) {
|
Chris@14
|
187 return $priority;
|
Chris@0
|
188 }
|
Chris@0
|
189 }
|
Chris@0
|
190 }
|
Chris@0
|
191 }
|
Chris@0
|
192
|
Chris@0
|
193 /**
|
Chris@0
|
194 * {@inheritdoc}
|
Chris@0
|
195 */
|
Chris@0
|
196 public function hasListeners($event_name = NULL) {
|
Chris@14
|
197 if ($event_name !== NULL) {
|
Chris@14
|
198 return !empty($this->listeners[$event_name]);
|
Chris@14
|
199 }
|
Chris@14
|
200
|
Chris@14
|
201 foreach ($this->listeners as $event_listeners) {
|
Chris@14
|
202 if ($event_listeners) {
|
Chris@14
|
203 return TRUE;
|
Chris@14
|
204 }
|
Chris@14
|
205 }
|
Chris@14
|
206
|
Chris@14
|
207 return FALSE;
|
Chris@0
|
208 }
|
Chris@0
|
209
|
Chris@0
|
210 /**
|
Chris@0
|
211 * {@inheritdoc}
|
Chris@0
|
212 */
|
Chris@0
|
213 public function addListener($event_name, $listener, $priority = 0) {
|
Chris@0
|
214 $this->listeners[$event_name][$priority][] = ['callable' => $listener];
|
Chris@0
|
215 $this->unsorted[$event_name] = TRUE;
|
Chris@0
|
216 }
|
Chris@0
|
217
|
Chris@0
|
218 /**
|
Chris@0
|
219 * {@inheritdoc}
|
Chris@0
|
220 */
|
Chris@0
|
221 public function removeListener($event_name, $listener) {
|
Chris@0
|
222 if (!isset($this->listeners[$event_name])) {
|
Chris@0
|
223 return;
|
Chris@0
|
224 }
|
Chris@0
|
225
|
Chris@0
|
226 foreach ($this->listeners[$event_name] as $priority => $definitions) {
|
Chris@0
|
227 foreach ($definitions as $key => $definition) {
|
Chris@0
|
228 if (!isset($definition['callable'])) {
|
Chris@0
|
229 if (!$this->container->initialized($definition['service'][0])) {
|
Chris@0
|
230 continue;
|
Chris@0
|
231 }
|
Chris@0
|
232 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
|
Chris@0
|
233 }
|
Chris@0
|
234
|
Chris@14
|
235 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure && !$listener instanceof \Closure) {
|
Chris@14
|
236 $definition['callable'][0] = $definition['callable'][0]();
|
Chris@14
|
237 }
|
Chris@14
|
238
|
Chris@14
|
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) {
|
Chris@14
|
240 $listener[0] = $listener[0]();
|
Chris@14
|
241 }
|
Chris@0
|
242 if ($definition['callable'] === $listener) {
|
Chris@14
|
243 unset($definitions[$key]);
|
Chris@0
|
244 }
|
Chris@0
|
245 }
|
Chris@14
|
246 if ($definitions) {
|
Chris@14
|
247 $this->listeners[$event_name][$priority] = $definitions;
|
Chris@14
|
248 }
|
Chris@14
|
249 else {
|
Chris@14
|
250 unset($this->listeners[$event_name][$priority]);
|
Chris@14
|
251 }
|
Chris@0
|
252 }
|
Chris@0
|
253 }
|
Chris@0
|
254
|
Chris@0
|
255 /**
|
Chris@0
|
256 * {@inheritdoc}
|
Chris@0
|
257 */
|
Chris@0
|
258 public function addSubscriber(EventSubscriberInterface $subscriber) {
|
Chris@0
|
259 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
|
Chris@0
|
260 if (is_string($params)) {
|
Chris@0
|
261 $this->addListener($event_name, [$subscriber, $params]);
|
Chris@0
|
262 }
|
Chris@0
|
263 elseif (is_string($params[0])) {
|
Chris@0
|
264 $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
|
Chris@0
|
265 }
|
Chris@0
|
266 else {
|
Chris@0
|
267 foreach ($params as $listener) {
|
Chris@0
|
268 $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
|
Chris@0
|
269 }
|
Chris@0
|
270 }
|
Chris@0
|
271 }
|
Chris@0
|
272 }
|
Chris@0
|
273
|
Chris@0
|
274 /**
|
Chris@0
|
275 * {@inheritdoc}
|
Chris@0
|
276 */
|
Chris@0
|
277 public function removeSubscriber(EventSubscriberInterface $subscriber) {
|
Chris@0
|
278 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
|
Chris@0
|
279 if (is_array($params) && is_array($params[0])) {
|
Chris@0
|
280 foreach ($params as $listener) {
|
Chris@0
|
281 $this->removeListener($event_name, [$subscriber, $listener[0]]);
|
Chris@0
|
282 }
|
Chris@0
|
283 }
|
Chris@0
|
284 else {
|
Chris@0
|
285 $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);
|
Chris@0
|
286 }
|
Chris@0
|
287 }
|
Chris@0
|
288 }
|
Chris@0
|
289
|
Chris@0
|
290 }
|