4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\EventDispatcher\Debug;
14 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
15 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16 use Symfony\Component\EventDispatcher\Event;
17 use Symfony\Component\Stopwatch\Stopwatch;
18 use Psr\Log\LoggerInterface;
21 * Collects some data about event listeners.
23 * This event dispatcher delegates the dispatching to another one.
25 * @author Fabien Potencier <fabien@symfony.com>
27 class TraceableEventDispatcher implements TraceableEventDispatcherInterface
34 private $wrappedListeners;
36 public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
38 $this->dispatcher = $dispatcher;
39 $this->stopwatch = $stopwatch;
40 $this->logger = $logger;
41 $this->called = array();
42 $this->wrappedListeners = array();
48 public function addListener($eventName, $listener, $priority = 0)
50 $this->dispatcher->addListener($eventName, $listener, $priority);
56 public function addSubscriber(EventSubscriberInterface $subscriber)
58 $this->dispatcher->addSubscriber($subscriber);
64 public function removeListener($eventName, $listener)
66 if (isset($this->wrappedListeners[$eventName])) {
67 foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
68 if ($wrappedListener->getWrappedListener() === $listener) {
69 $listener = $wrappedListener;
70 unset($this->wrappedListeners[$eventName][$index]);
76 return $this->dispatcher->removeListener($eventName, $listener);
82 public function removeSubscriber(EventSubscriberInterface $subscriber)
84 return $this->dispatcher->removeSubscriber($subscriber);
90 public function getListeners($eventName = null)
92 return $this->dispatcher->getListeners($eventName);
98 public function getListenerPriority($eventName, $listener)
100 // we might have wrapped listeners for the event (if called while dispatching)
101 // in that case get the priority by wrapper
102 if (isset($this->wrappedListeners[$eventName])) {
103 foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
104 if ($wrappedListener->getWrappedListener() === $listener) {
105 return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
110 return $this->dispatcher->getListenerPriority($eventName, $listener);
116 public function hasListeners($eventName = null)
118 return $this->dispatcher->hasListeners($eventName);
124 public function dispatch($eventName, Event $event = null)
126 if (null === $event) {
127 $event = new Event();
130 if (null !== $this->logger && $event->isPropagationStopped()) {
131 $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
134 $this->preProcess($eventName);
135 $this->preDispatch($eventName, $event);
137 $e = $this->stopwatch->start($eventName, 'section');
139 $this->dispatcher->dispatch($eventName, $event);
141 if ($e->isStarted()) {
145 $this->postDispatch($eventName, $event);
146 $this->postProcess($eventName);
154 public function getCalledListeners()
157 foreach ($this->called as $eventName => $listeners) {
158 foreach ($listeners as $listener) {
159 $called[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName);
169 public function getNotCalledListeners()
172 $allListeners = $this->getListeners();
173 } catch (\Exception $e) {
174 if (null !== $this->logger) {
175 $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
178 // unable to retrieve the uncalled listeners
182 $notCalled = array();
183 foreach ($allListeners as $eventName => $listeners) {
184 foreach ($listeners as $listener) {
186 if (isset($this->called[$eventName])) {
187 foreach ($this->called[$eventName] as $l) {
188 if ($l->getWrappedListener() === $listener) {
197 if (!$listener instanceof WrappedListener) {
198 $listener = new WrappedListener($listener, null, $this->stopwatch, $this);
200 $notCalled[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName);
205 uasort($notCalled, array($this, 'sortListenersByPriority'));
210 public function reset()
212 $this->called = array();
216 * Proxies all method calls to the original event dispatcher.
218 * @param string $method The method name
219 * @param array $arguments The method arguments
223 public function __call($method, $arguments)
225 return call_user_func_array(array($this->dispatcher, $method), $arguments);
229 * Called before dispatching the event.
231 * @param string $eventName The event name
232 * @param Event $event The event
234 protected function preDispatch($eventName, Event $event)
239 * Called after dispatching the event.
241 * @param string $eventName The event name
242 * @param Event $event The event
244 protected function postDispatch($eventName, Event $event)
248 private function preProcess($eventName)
250 foreach ($this->dispatcher->getListeners($eventName) as $listener) {
251 $priority = $this->getListenerPriority($eventName, $listener);
252 $wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this);
253 $this->wrappedListeners[$eventName][] = $wrappedListener;
254 $this->dispatcher->removeListener($eventName, $listener);
255 $this->dispatcher->addListener($eventName, $wrappedListener, $priority);
259 private function postProcess($eventName)
261 unset($this->wrappedListeners[$eventName]);
263 foreach ($this->dispatcher->getListeners($eventName) as $listener) {
264 if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
268 $priority = $this->getListenerPriority($eventName, $listener);
269 $this->dispatcher->removeListener($eventName, $listener);
270 $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
272 if (null !== $this->logger) {
273 $context = array('event' => $eventName, 'listener' => $listener->getPretty());
276 if ($listener->wasCalled()) {
277 if (null !== $this->logger) {
278 $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
281 if (!isset($this->called[$eventName])) {
282 $this->called[$eventName] = new \SplObjectStorage();
285 $this->called[$eventName]->attach($listener);
288 if (null !== $this->logger && $skipped) {
289 $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
292 if ($listener->stoppedPropagation()) {
293 if (null !== $this->logger) {
294 $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
302 private function sortListenersByPriority($a, $b)
304 if (is_int($a['priority']) && !is_int($b['priority'])) {
308 if (!is_int($a['priority']) && is_int($b['priority'])) {
312 if ($a['priority'] === $b['priority']) {
316 if ($a['priority'] > $b['priority']) {