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