Skip to content

Commit 8c08046

Browse files
committed
[EventDispatcher] Added TraceableEventDispatcher from HttpKernel
1 parent 9e389e6 commit 8c08046

File tree

2 files changed

+9
-478
lines changed

2 files changed

+9
-478
lines changed

Debug/TraceableEventDispatcher.php

Lines changed: 9 additions & 326 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,10 @@
1111

1212
namespace Symfony\Component\HttpKernel\Debug;
1313

14-
use Symfony\Component\Stopwatch\Stopwatch;
15-
use Symfony\Component\HttpKernel\KernelEvents;
14+
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher;
1615
use Symfony\Component\HttpKernel\Profiler\Profiler;
17-
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\HttpKernel\KernelEvents;
1817
use Symfony\Component\EventDispatcher\Event;
19-
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
20-
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
21-
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
2218

2319
/**
2420
* Collects some data about event listeners.
@@ -27,31 +23,8 @@
2723
*
2824
* @author Fabien Potencier <[email protected]>
2925
*/
30-
class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEventDispatcherInterface
26+
class TraceableEventDispatcher extends BaseTraceableEventDispatcher
3127
{
32-
private $logger;
33-
private $called = array();
34-
private $stopwatch;
35-
private $dispatcher;
36-
private $wrappedListeners = array();
37-
private $firstCalledEvent = array();
38-
private $id;
39-
private $lastEventId = 0;
40-
41-
/**
42-
* Constructor.
43-
*
44-
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
45-
* @param Stopwatch $stopwatch A Stopwatch instance
46-
* @param LoggerInterface $logger A LoggerInterface instance
47-
*/
48-
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
49-
{
50-
$this->dispatcher = $dispatcher;
51-
$this->stopwatch = $stopwatch;
52-
$this->logger = $logger;
53-
}
54-
5528
/**
5629
* Sets the profiler.
5730
*
@@ -67,268 +40,11 @@ public function setProfiler(Profiler $profiler = null)
6740
{
6841
}
6942

70-
/**
71-
* {@inheritDoc}
72-
*/
73-
public function addListener($eventName, $listener, $priority = 0)
74-
{
75-
$this->dispatcher->addListener($eventName, $listener, $priority);
76-
}
77-
78-
/**
79-
* {@inheritdoc}
80-
*/
81-
public function addSubscriber(EventSubscriberInterface $subscriber)
82-
{
83-
$this->dispatcher->addSubscriber($subscriber);
84-
}
85-
86-
/**
87-
* {@inheritdoc}
88-
*/
89-
public function removeListener($eventName, $listener)
90-
{
91-
return $this->dispatcher->removeListener($eventName, $listener);
92-
}
93-
94-
/**
95-
* {@inheritdoc}
96-
*/
97-
public function removeSubscriber(EventSubscriberInterface $subscriber)
98-
{
99-
return $this->dispatcher->removeSubscriber($subscriber);
100-
}
101-
10243
/**
10344
* {@inheritdoc}
10445
*/
105-
public function getListeners($eventName = null)
46+
protected function preDispatch($eventName, Event $event)
10647
{
107-
return $this->dispatcher->getListeners($eventName);
108-
}
109-
110-
/**
111-
* {@inheritdoc}
112-
*/
113-
public function hasListeners($eventName = null)
114-
{
115-
return $this->dispatcher->hasListeners($eventName);
116-
}
117-
118-
/**
119-
* {@inheritdoc}
120-
*/
121-
public function dispatch($eventName, Event $event = null)
122-
{
123-
if (null === $event) {
124-
$event = new Event();
125-
}
126-
127-
$this->id = $eventId = ++$this->lastEventId;
128-
129-
$this->preDispatch($eventName, $event);
130-
131-
$e = $this->stopwatch->start($eventName, 'section');
132-
133-
$this->firstCalledEvent[$eventName] = $this->stopwatch->start($eventName.'.loading', 'event_listener_loading');
134-
135-
if (!$this->dispatcher->hasListeners($eventName)) {
136-
$this->firstCalledEvent[$eventName]->stop();
137-
}
138-
139-
$this->dispatcher->dispatch($eventName, $event);
140-
141-
// reset the id as another event might have been dispatched during the dispatching of this event
142-
$this->id = $eventId;
143-
144-
unset($this->firstCalledEvent[$eventName]);
145-
146-
if ($e->isStarted()) {
147-
$e->stop();
148-
}
149-
150-
$this->postDispatch($eventName, $event);
151-
152-
return $event;
153-
}
154-
155-
/**
156-
* {@inheritDoc}
157-
*/
158-
public function getCalledListeners()
159-
{
160-
return $this->called;
161-
}
162-
163-
/**
164-
* {@inheritDoc}
165-
*/
166-
public function getNotCalledListeners()
167-
{
168-
$notCalled = array();
169-
170-
foreach ($this->getListeners() as $name => $listeners) {
171-
foreach ($listeners as $listener) {
172-
$info = $this->getListenerInfo($listener, $name);
173-
if (!isset($this->called[$name.'.'.$info['pretty']])) {
174-
$notCalled[$name.'.'.$info['pretty']] = $info;
175-
}
176-
}
177-
}
178-
179-
return $notCalled;
180-
}
181-
182-
/**
183-
* Proxies all method calls to the original event dispatcher.
184-
*
185-
* @param string $method The method name
186-
* @param array $arguments The method arguments
187-
*
188-
* @return mixed
189-
*/
190-
public function __call($method, $arguments)
191-
{
192-
return call_user_func_array(array($this->dispatcher, $method), $arguments);
193-
}
194-
195-
/**
196-
* This is a private method and must not be used.
197-
*
198-
* This method is public because it is used in a closure.
199-
* Whenever Symfony will require PHP 5.4, this could be changed
200-
* to a proper private method.
201-
*/
202-
public function logSkippedListeners($eventName, Event $event, $listener)
203-
{
204-
if (null === $this->logger) {
205-
return;
206-
}
207-
208-
$info = $this->getListenerInfo($listener, $eventName);
209-
210-
$this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
211-
212-
$skippedListeners = $this->getListeners($eventName);
213-
$skipped = false;
214-
215-
foreach ($skippedListeners as $skippedListener) {
216-
$skippedListener = $this->unwrapListener($skippedListener);
217-
218-
if ($skipped) {
219-
$info = $this->getListenerInfo($skippedListener, $eventName);
220-
$this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
221-
}
222-
223-
if ($skippedListener === $listener) {
224-
$skipped = true;
225-
}
226-
}
227-
}
228-
229-
/**
230-
* This is a private method.
231-
*
232-
* This method is public because it is used in a closure.
233-
* Whenever Symfony will require PHP 5.4, this could be changed
234-
* to a proper private method.
235-
*/
236-
public function preListenerCall($eventName, $listener)
237-
{
238-
// is it the first called listener?
239-
if (isset($this->firstCalledEvent[$eventName])) {
240-
$this->firstCalledEvent[$eventName]->stop();
241-
242-
unset($this->firstCalledEvent[$eventName]);
243-
}
244-
245-
$info = $this->getListenerInfo($listener, $eventName);
246-
247-
if (null !== $this->logger) {
248-
$this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
249-
}
250-
251-
$this->called[$eventName.'.'.$info['pretty']] = $info;
252-
253-
return $this->stopwatch->start(isset($info['class']) ? $info['class'] : $info['type'], 'event_listener');
254-
}
255-
256-
/**
257-
* Returns information about the listener
258-
*
259-
* @param object $listener The listener
260-
* @param string $eventName The event name
261-
*
262-
* @return array Informations about the listener
263-
*/
264-
private function getListenerInfo($listener, $eventName)
265-
{
266-
$listener = $this->unwrapListener($listener);
267-
268-
$info = array(
269-
'event' => $eventName,
270-
);
271-
if ($listener instanceof \Closure) {
272-
$info += array(
273-
'type' => 'Closure',
274-
'pretty' => 'closure'
275-
);
276-
} elseif (is_string($listener)) {
277-
try {
278-
$r = new \ReflectionFunction($listener);
279-
$file = $r->getFileName();
280-
$line = $r->getStartLine();
281-
} catch (\ReflectionException $e) {
282-
$file = null;
283-
$line = null;
284-
}
285-
$info += array(
286-
'type' => 'Function',
287-
'function' => $listener,
288-
'file' => $file,
289-
'line' => $line,
290-
'pretty' => $listener,
291-
);
292-
} elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) {
293-
if (!is_array($listener)) {
294-
$listener = array($listener, '__invoke');
295-
}
296-
$class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
297-
try {
298-
$r = new \ReflectionMethod($class, $listener[1]);
299-
$file = $r->getFileName();
300-
$line = $r->getStartLine();
301-
} catch (\ReflectionException $e) {
302-
$file = null;
303-
$line = null;
304-
}
305-
$info += array(
306-
'type' => 'Method',
307-
'class' => $class,
308-
'method' => $listener[1],
309-
'file' => $file,
310-
'line' => $line,
311-
'pretty' => $class.'::'.$listener[1],
312-
);
313-
}
314-
315-
return $info;
316-
}
317-
318-
private function preDispatch($eventName, Event $event)
319-
{
320-
// wrap all listeners before they are called
321-
$this->wrappedListeners[$this->id] = new \SplObjectStorage();
322-
323-
$listeners = $this->dispatcher->getListeners($eventName);
324-
325-
foreach ($listeners as $listener) {
326-
$this->dispatcher->removeListener($eventName, $listener);
327-
$wrapped = $this->wrapListener($eventName, $listener);
328-
$this->wrappedListeners[$this->id][$wrapped] = $listener;
329-
$this->dispatcher->addListener($eventName, $wrapped);
330-
}
331-
33248
switch ($eventName) {
33349
case KernelEvents::REQUEST:
33450
$this->stopwatch->openSection();
@@ -342,7 +58,7 @@ private function preDispatch($eventName, Event $event)
34258
break;
34359
case KernelEvents::TERMINATE:
34460
$token = $event->getResponse()->headers->get('X-Debug-Token');
345-
// There is a very special case when using builtin AppCache class as kernel wrapper, in the case
61+
// There is a very special case when using built-in AppCache class as kernel wrapper, in the case
34662
// of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A].
34763
// In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID
34864
// is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception
@@ -354,7 +70,10 @@ private function preDispatch($eventName, Event $event)
35470
}
35571
}
35672

357-
private function postDispatch($eventName, Event $event)
73+
/**
74+
* {@inheritdoc}
75+
*/
76+
protected function postDispatch($eventName, Event $event)
35877
{
35978
switch ($eventName) {
36079
case KernelEvents::CONTROLLER:
@@ -373,41 +92,5 @@ private function postDispatch($eventName, Event $event)
37392
} catch (\LogicException $e) {}
37493
break;
37594
}
376-
377-
foreach ($this->wrappedListeners[$this->id] as $wrapped) {
378-
$this->dispatcher->removeListener($eventName, $wrapped);
379-
$this->dispatcher->addListener($eventName, $this->wrappedListeners[$this->id][$wrapped]);
380-
}
381-
382-
unset($this->wrappedListeners[$this->id]);
383-
}
384-
385-
private function wrapListener($eventName, $listener)
386-
{
387-
$self = $this;
388-
389-
return function (Event $event) use ($self, $eventName, $listener) {
390-
$e = $self->preListenerCall($eventName, $listener);
391-
392-
call_user_func($listener, $event, $eventName, $self);
393-
394-
if ($e->isStarted()) {
395-
$e->stop();
396-
}
397-
398-
if ($event->isPropagationStopped()) {
399-
$self->logSkippedListeners($eventName, $event, $listener);
400-
}
401-
};
402-
}
403-
404-
private function unwrapListener($listener)
405-
{
406-
// get the original listener
407-
if (is_object($listener) && isset($this->wrappedListeners[$this->id][$listener])) {
408-
return $this->wrappedListeners[$this->id][$listener];
409-
}
410-
411-
return $listener;
41295
}
41396
}

0 commit comments

Comments
 (0)