Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\EventDispatcher\Debug;
|
Chris@0
|
13
|
Chris@17
|
14 use Psr\Log\LoggerInterface;
|
Chris@17
|
15 use Symfony\Component\EventDispatcher\Event;
|
Chris@0
|
16 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
Chris@0
|
17 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
Chris@0
|
18 use Symfony\Component\Stopwatch\Stopwatch;
|
Chris@0
|
19
|
Chris@0
|
20 /**
|
Chris@0
|
21 * Collects some data about event listeners.
|
Chris@0
|
22 *
|
Chris@0
|
23 * This event dispatcher delegates the dispatching to another one.
|
Chris@0
|
24 *
|
Chris@0
|
25 * @author Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
26 */
|
Chris@0
|
27 class TraceableEventDispatcher implements TraceableEventDispatcherInterface
|
Chris@0
|
28 {
|
Chris@0
|
29 protected $logger;
|
Chris@0
|
30 protected $stopwatch;
|
Chris@0
|
31
|
Chris@17
|
32 private $callStack;
|
Chris@0
|
33 private $dispatcher;
|
Chris@0
|
34 private $wrappedListeners;
|
Chris@0
|
35
|
Chris@0
|
36 public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
|
Chris@0
|
37 {
|
Chris@0
|
38 $this->dispatcher = $dispatcher;
|
Chris@0
|
39 $this->stopwatch = $stopwatch;
|
Chris@0
|
40 $this->logger = $logger;
|
Chris@17
|
41 $this->wrappedListeners = [];
|
Chris@0
|
42 }
|
Chris@0
|
43
|
Chris@0
|
44 /**
|
Chris@0
|
45 * {@inheritdoc}
|
Chris@0
|
46 */
|
Chris@0
|
47 public function addListener($eventName, $listener, $priority = 0)
|
Chris@0
|
48 {
|
Chris@0
|
49 $this->dispatcher->addListener($eventName, $listener, $priority);
|
Chris@0
|
50 }
|
Chris@0
|
51
|
Chris@0
|
52 /**
|
Chris@0
|
53 * {@inheritdoc}
|
Chris@0
|
54 */
|
Chris@0
|
55 public function addSubscriber(EventSubscriberInterface $subscriber)
|
Chris@0
|
56 {
|
Chris@0
|
57 $this->dispatcher->addSubscriber($subscriber);
|
Chris@0
|
58 }
|
Chris@0
|
59
|
Chris@0
|
60 /**
|
Chris@0
|
61 * {@inheritdoc}
|
Chris@0
|
62 */
|
Chris@0
|
63 public function removeListener($eventName, $listener)
|
Chris@0
|
64 {
|
Chris@0
|
65 if (isset($this->wrappedListeners[$eventName])) {
|
Chris@0
|
66 foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
|
Chris@0
|
67 if ($wrappedListener->getWrappedListener() === $listener) {
|
Chris@0
|
68 $listener = $wrappedListener;
|
Chris@0
|
69 unset($this->wrappedListeners[$eventName][$index]);
|
Chris@0
|
70 break;
|
Chris@0
|
71 }
|
Chris@0
|
72 }
|
Chris@0
|
73 }
|
Chris@0
|
74
|
Chris@0
|
75 return $this->dispatcher->removeListener($eventName, $listener);
|
Chris@0
|
76 }
|
Chris@0
|
77
|
Chris@0
|
78 /**
|
Chris@0
|
79 * {@inheritdoc}
|
Chris@0
|
80 */
|
Chris@0
|
81 public function removeSubscriber(EventSubscriberInterface $subscriber)
|
Chris@0
|
82 {
|
Chris@0
|
83 return $this->dispatcher->removeSubscriber($subscriber);
|
Chris@0
|
84 }
|
Chris@0
|
85
|
Chris@0
|
86 /**
|
Chris@0
|
87 * {@inheritdoc}
|
Chris@0
|
88 */
|
Chris@0
|
89 public function getListeners($eventName = null)
|
Chris@0
|
90 {
|
Chris@0
|
91 return $this->dispatcher->getListeners($eventName);
|
Chris@0
|
92 }
|
Chris@0
|
93
|
Chris@0
|
94 /**
|
Chris@0
|
95 * {@inheritdoc}
|
Chris@0
|
96 */
|
Chris@0
|
97 public function getListenerPriority($eventName, $listener)
|
Chris@0
|
98 {
|
Chris@0
|
99 // we might have wrapped listeners for the event (if called while dispatching)
|
Chris@0
|
100 // in that case get the priority by wrapper
|
Chris@0
|
101 if (isset($this->wrappedListeners[$eventName])) {
|
Chris@0
|
102 foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
|
Chris@0
|
103 if ($wrappedListener->getWrappedListener() === $listener) {
|
Chris@0
|
104 return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
|
Chris@0
|
105 }
|
Chris@0
|
106 }
|
Chris@0
|
107 }
|
Chris@0
|
108
|
Chris@0
|
109 return $this->dispatcher->getListenerPriority($eventName, $listener);
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 /**
|
Chris@0
|
113 * {@inheritdoc}
|
Chris@0
|
114 */
|
Chris@0
|
115 public function hasListeners($eventName = null)
|
Chris@0
|
116 {
|
Chris@0
|
117 return $this->dispatcher->hasListeners($eventName);
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120 /**
|
Chris@0
|
121 * {@inheritdoc}
|
Chris@0
|
122 */
|
Chris@0
|
123 public function dispatch($eventName, Event $event = null)
|
Chris@0
|
124 {
|
Chris@17
|
125 if (null === $this->callStack) {
|
Chris@17
|
126 $this->callStack = new \SplObjectStorage();
|
Chris@17
|
127 }
|
Chris@17
|
128
|
Chris@0
|
129 if (null === $event) {
|
Chris@0
|
130 $event = new Event();
|
Chris@0
|
131 }
|
Chris@0
|
132
|
Chris@0
|
133 if (null !== $this->logger && $event->isPropagationStopped()) {
|
Chris@0
|
134 $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
|
Chris@0
|
135 }
|
Chris@0
|
136
|
Chris@0
|
137 $this->preProcess($eventName);
|
Chris@17
|
138 try {
|
Chris@17
|
139 $this->preDispatch($eventName, $event);
|
Chris@17
|
140 try {
|
Chris@17
|
141 $e = $this->stopwatch->start($eventName, 'section');
|
Chris@17
|
142 try {
|
Chris@17
|
143 $this->dispatcher->dispatch($eventName, $event);
|
Chris@17
|
144 } finally {
|
Chris@17
|
145 if ($e->isStarted()) {
|
Chris@17
|
146 $e->stop();
|
Chris@17
|
147 }
|
Chris@17
|
148 }
|
Chris@17
|
149 } finally {
|
Chris@17
|
150 $this->postDispatch($eventName, $event);
|
Chris@17
|
151 }
|
Chris@17
|
152 } finally {
|
Chris@17
|
153 $this->postProcess($eventName);
|
Chris@0
|
154 }
|
Chris@0
|
155
|
Chris@0
|
156 return $event;
|
Chris@0
|
157 }
|
Chris@0
|
158
|
Chris@0
|
159 /**
|
Chris@0
|
160 * {@inheritdoc}
|
Chris@0
|
161 */
|
Chris@0
|
162 public function getCalledListeners()
|
Chris@0
|
163 {
|
Chris@17
|
164 if (null === $this->callStack) {
|
Chris@17
|
165 return [];
|
Chris@17
|
166 }
|
Chris@17
|
167
|
Chris@17
|
168 $called = [];
|
Chris@17
|
169 foreach ($this->callStack as $listener) {
|
Chris@17
|
170 list($eventName) = $this->callStack->getInfo();
|
Chris@17
|
171
|
Chris@17
|
172 $called[] = $listener->getInfo($eventName);
|
Chris@0
|
173 }
|
Chris@0
|
174
|
Chris@0
|
175 return $called;
|
Chris@0
|
176 }
|
Chris@0
|
177
|
Chris@0
|
178 /**
|
Chris@0
|
179 * {@inheritdoc}
|
Chris@0
|
180 */
|
Chris@0
|
181 public function getNotCalledListeners()
|
Chris@0
|
182 {
|
Chris@0
|
183 try {
|
Chris@0
|
184 $allListeners = $this->getListeners();
|
Chris@0
|
185 } catch (\Exception $e) {
|
Chris@0
|
186 if (null !== $this->logger) {
|
Chris@17
|
187 $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
|
Chris@0
|
188 }
|
Chris@0
|
189
|
Chris@0
|
190 // unable to retrieve the uncalled listeners
|
Chris@17
|
191 return [];
|
Chris@0
|
192 }
|
Chris@0
|
193
|
Chris@17
|
194 $notCalled = [];
|
Chris@0
|
195 foreach ($allListeners as $eventName => $listeners) {
|
Chris@0
|
196 foreach ($listeners as $listener) {
|
Chris@0
|
197 $called = false;
|
Chris@17
|
198 if (null !== $this->callStack) {
|
Chris@17
|
199 foreach ($this->callStack as $calledListener) {
|
Chris@17
|
200 if ($calledListener->getWrappedListener() === $listener) {
|
Chris@0
|
201 $called = true;
|
Chris@0
|
202
|
Chris@0
|
203 break;
|
Chris@0
|
204 }
|
Chris@0
|
205 }
|
Chris@0
|
206 }
|
Chris@0
|
207
|
Chris@0
|
208 if (!$called) {
|
Chris@0
|
209 if (!$listener instanceof WrappedListener) {
|
Chris@0
|
210 $listener = new WrappedListener($listener, null, $this->stopwatch, $this);
|
Chris@0
|
211 }
|
Chris@17
|
212 $notCalled[] = $listener->getInfo($eventName);
|
Chris@0
|
213 }
|
Chris@0
|
214 }
|
Chris@0
|
215 }
|
Chris@0
|
216
|
Chris@17
|
217 uasort($notCalled, [$this, 'sortNotCalledListeners']);
|
Chris@0
|
218
|
Chris@0
|
219 return $notCalled;
|
Chris@0
|
220 }
|
Chris@0
|
221
|
Chris@14
|
222 public function reset()
|
Chris@14
|
223 {
|
Chris@17
|
224 $this->callStack = null;
|
Chris@14
|
225 }
|
Chris@14
|
226
|
Chris@0
|
227 /**
|
Chris@0
|
228 * Proxies all method calls to the original event dispatcher.
|
Chris@0
|
229 *
|
Chris@0
|
230 * @param string $method The method name
|
Chris@0
|
231 * @param array $arguments The method arguments
|
Chris@0
|
232 *
|
Chris@0
|
233 * @return mixed
|
Chris@0
|
234 */
|
Chris@0
|
235 public function __call($method, $arguments)
|
Chris@0
|
236 {
|
Chris@17
|
237 return \call_user_func_array([$this->dispatcher, $method], $arguments);
|
Chris@0
|
238 }
|
Chris@0
|
239
|
Chris@0
|
240 /**
|
Chris@0
|
241 * Called before dispatching the event.
|
Chris@0
|
242 *
|
Chris@0
|
243 * @param string $eventName The event name
|
Chris@0
|
244 * @param Event $event The event
|
Chris@0
|
245 */
|
Chris@0
|
246 protected function preDispatch($eventName, Event $event)
|
Chris@0
|
247 {
|
Chris@0
|
248 }
|
Chris@0
|
249
|
Chris@0
|
250 /**
|
Chris@0
|
251 * Called after dispatching the event.
|
Chris@0
|
252 *
|
Chris@0
|
253 * @param string $eventName The event name
|
Chris@0
|
254 * @param Event $event The event
|
Chris@0
|
255 */
|
Chris@0
|
256 protected function postDispatch($eventName, Event $event)
|
Chris@0
|
257 {
|
Chris@0
|
258 }
|
Chris@0
|
259
|
Chris@0
|
260 private function preProcess($eventName)
|
Chris@0
|
261 {
|
Chris@0
|
262 foreach ($this->dispatcher->getListeners($eventName) as $listener) {
|
Chris@0
|
263 $priority = $this->getListenerPriority($eventName, $listener);
|
Chris@17
|
264 $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);
|
Chris@0
|
265 $this->wrappedListeners[$eventName][] = $wrappedListener;
|
Chris@0
|
266 $this->dispatcher->removeListener($eventName, $listener);
|
Chris@0
|
267 $this->dispatcher->addListener($eventName, $wrappedListener, $priority);
|
Chris@17
|
268 $this->callStack->attach($wrappedListener, [$eventName]);
|
Chris@0
|
269 }
|
Chris@0
|
270 }
|
Chris@0
|
271
|
Chris@0
|
272 private function postProcess($eventName)
|
Chris@0
|
273 {
|
Chris@0
|
274 unset($this->wrappedListeners[$eventName]);
|
Chris@0
|
275 $skipped = false;
|
Chris@0
|
276 foreach ($this->dispatcher->getListeners($eventName) as $listener) {
|
Chris@0
|
277 if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
|
Chris@0
|
278 continue;
|
Chris@0
|
279 }
|
Chris@0
|
280 // Unwrap listener
|
Chris@0
|
281 $priority = $this->getListenerPriority($eventName, $listener);
|
Chris@0
|
282 $this->dispatcher->removeListener($eventName, $listener);
|
Chris@0
|
283 $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
|
Chris@0
|
284
|
Chris@0
|
285 if (null !== $this->logger) {
|
Chris@17
|
286 $context = ['event' => $eventName, 'listener' => $listener->getPretty()];
|
Chris@0
|
287 }
|
Chris@0
|
288
|
Chris@0
|
289 if ($listener->wasCalled()) {
|
Chris@0
|
290 if (null !== $this->logger) {
|
Chris@0
|
291 $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
|
Chris@0
|
292 }
|
Chris@17
|
293 } else {
|
Chris@17
|
294 $this->callStack->detach($listener);
|
Chris@0
|
295 }
|
Chris@0
|
296
|
Chris@0
|
297 if (null !== $this->logger && $skipped) {
|
Chris@0
|
298 $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
|
Chris@0
|
299 }
|
Chris@0
|
300
|
Chris@0
|
301 if ($listener->stoppedPropagation()) {
|
Chris@0
|
302 if (null !== $this->logger) {
|
Chris@0
|
303 $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
|
Chris@0
|
304 }
|
Chris@0
|
305
|
Chris@0
|
306 $skipped = true;
|
Chris@0
|
307 }
|
Chris@0
|
308 }
|
Chris@0
|
309 }
|
Chris@0
|
310
|
Chris@17
|
311 private function sortNotCalledListeners(array $a, array $b)
|
Chris@0
|
312 {
|
Chris@17
|
313 if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
|
Chris@17
|
314 return $cmp;
|
Chris@17
|
315 }
|
Chris@17
|
316
|
Chris@17
|
317 if (\is_int($a['priority']) && !\is_int($b['priority'])) {
|
Chris@0
|
318 return 1;
|
Chris@0
|
319 }
|
Chris@0
|
320
|
Chris@17
|
321 if (!\is_int($a['priority']) && \is_int($b['priority'])) {
|
Chris@0
|
322 return -1;
|
Chris@0
|
323 }
|
Chris@0
|
324
|
Chris@0
|
325 if ($a['priority'] === $b['priority']) {
|
Chris@0
|
326 return 0;
|
Chris@0
|
327 }
|
Chris@0
|
328
|
Chris@0
|
329 if ($a['priority'] > $b['priority']) {
|
Chris@0
|
330 return -1;
|
Chris@0
|
331 }
|
Chris@0
|
332
|
Chris@0
|
333 return 1;
|
Chris@0
|
334 }
|
Chris@0
|
335 }
|