From 42e4c7bddf9a00100a69e7914d2d9349b92c5b13 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 4 Feb 2014 15:06:59 +0100 Subject: [PATCH] [EventDispatcher] simplified code for TraceableEventDispatcher --- .../Debug/TraceableEventDispatcher.php | 220 ++++++------------ .../EventDispatcher/Debug/WrappedListener.php | 69 ++++++ .../Debug/TraceableEventDispatcherTest.php | 4 - 3 files changed, 146 insertions(+), 147 deletions(-) create mode 100644 src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php diff --git a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php index 97857c6137f8..5f0781ebedc3 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php @@ -28,11 +28,9 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface { protected $logger; protected $stopwatch; - private $called = array(); + + private $called; private $dispatcher; - private $wrappedListeners = array(); - private $firstCalledEvent = array(); - private $lastEventId = 0; /** * Constructor. @@ -46,6 +44,7 @@ public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $sto $this->dispatcher = $dispatcher; $this->stopwatch = $stopwatch; $this->logger = $logger; + $this->called = array(); } /** @@ -105,47 +104,19 @@ public function dispatch($eventName, Event $event = null) $event = new Event(); } - $eventId = ++$this->lastEventId; - - // Wrap all listeners before they are called - $this->wrappedListeners[$eventId] = new \SplObjectStorage(); - - $listeners = $this->dispatcher->getListeners($eventName); - - foreach ($listeners as $listener) { - $this->dispatcher->removeListener($eventName, $listener); - $wrapped = $this->wrapListener($eventName, $eventId, $listener); - $this->wrappedListeners[$eventId][$wrapped] = $listener; - $this->dispatcher->addListener($eventName, $wrapped); - } - + $this->preProcess($eventName); $this->preDispatch($eventName, $event); $e = $this->stopwatch->start($eventName, 'section'); - $this->firstCalledEvent[$eventName] = $this->stopwatch->start($eventName.'.loading', 'event_listener_loading'); - - if (!$this->dispatcher->hasListeners($eventName)) { - $this->firstCalledEvent[$eventName]->stop(); - } - $this->dispatcher->dispatch($eventName, $event); - unset($this->firstCalledEvent[$eventName]); - if ($e->isStarted()) { $e->stop(); } $this->postDispatch($eventName, $event); - - // Unwrap all listeners after they are called - foreach ($this->wrappedListeners[$eventId] as $wrapped) { - $this->dispatcher->removeListener($eventName, $wrapped); - $this->dispatcher->addListener($eventName, $this->wrappedListeners[$eventId][$wrapped]); - } - - unset($this->wrappedListeners[$eventId]); + $this->postProcess($eventName); return $event; } @@ -155,7 +126,15 @@ public function dispatch($eventName, Event $event = null) */ public function getCalledListeners() { - return $this->called; + $called = array(); + foreach ($this->called as $eventName => $listeners) { + foreach ($listeners as $listener) { + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + $called[$eventName.'.'.$info['pretty']] = $info; + } + } + + return $called; } /** @@ -164,12 +143,22 @@ public function getCalledListeners() public function getNotCalledListeners() { $notCalled = array(); - - foreach ($this->getListeners() as $name => $listeners) { + foreach ($this->getListeners() as $eventName => $listeners) { foreach ($listeners as $listener) { - $info = $this->getListenerInfo($listener, $name, null); - if (!isset($this->called[$name.'.'.$info['pretty']])) { - $notCalled[$name.'.'.$info['pretty']] = $info; + $called = false; + if (isset($this->called[$eventName])) { + foreach ($this->called[$eventName] as $l) { + if ($l->getWrappedListener() === $listener) { + $called = true; + + break; + } + } + } + + if (!$called) { + $info = $this->getListenerInfo($listener, $eventName); + $notCalled[$eventName.'.'.$info['pretty']] = $info; } } } @@ -191,64 +180,68 @@ public function __call($method, $arguments) } /** - * This is a private method and must not be used. + * Called before dispatching the event. * - * This method is public because it is used in a closure. - * Whenever Symfony will require PHP 5.4, this could be changed - * to a proper private method. + * @param string $eventName The event name + * @param Event $event The event */ - public function logSkippedListeners($eventName, $eventId, Event $event, $listener) + protected function preDispatch($eventName, Event $event) { - if (null === $this->logger) { - return; - } - - $info = $this->getListenerInfo($listener, $eventName, $eventId); - - $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName)); - - $skippedListeners = $this->getListeners($eventName); - $skipped = false; - - foreach ($skippedListeners as $skippedListener) { - $skippedListener = $this->unwrapListener($skippedListener, $eventId); - - if ($skipped) { - $info = $this->getListenerInfo($skippedListener, $eventName, $eventId); - $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName)); - } - - if ($skippedListener === $listener) { - $skipped = true; - } - } } /** - * This is a private method. + * Called after dispatching the event. * - * This method is public because it is used in a closure. - * Whenever Symfony will require PHP 5.4, this could be changed - * to a proper private method. + * @param string $eventName The event name + * @param Event $event The event */ - public function preListenerCall($eventName, $eventId, $listener) + protected function postDispatch($eventName, Event $event) { - // is it the first called listener? - if (isset($this->firstCalledEvent[$eventName])) { - $this->firstCalledEvent[$eventName]->stop(); + } - unset($this->firstCalledEvent[$eventName]); + private function preProcess($eventName) + { + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $this->dispatcher->removeListener($eventName, $listener); + $info = $this->getListenerInfo($listener, $eventName); + $name = isset($info['class']) ? $info['class'] : $info['type']; + $this->dispatcher->addListener($eventName, new WrappedListener($listener, $name, $this->stopwatch)); } + } - $info = $this->getListenerInfo($listener, $eventName, $eventId); + private function postProcess($eventName) + { + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + // Unwrap listener + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener()); - if (null !== $this->logger) { - $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty'])); - } + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty'])); + } + + if (!isset($this->called[$eventName])) { + $this->called[$eventName] = new \SplObjectStorage(); + } - $this->called[$eventName.'.'.$info['pretty']] = $info; + $this->called[$eventName]->attach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName)); + } - return $this->stopwatch->start(isset($info['class']) ? $info['class'] : $info['type'], 'event_listener'); + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName)); + } + + $skipped = true; + } + } } /** @@ -259,10 +252,8 @@ public function preListenerCall($eventName, $eventId, $listener) * * @return array Information about the listener */ - private function getListenerInfo($listener, $eventName, $eventId) + private function getListenerInfo($listener, $eventName) { - $listener = $this->unwrapListener($listener, $eventId); - $info = array( 'event' => $eventName, ); @@ -312,61 +303,4 @@ private function getListenerInfo($listener, $eventName, $eventId) return $info; } - - /** - * Called before dispatching the event. - * - * @param string $eventName The event name - * @param Event $event The event - */ - protected function preDispatch($eventName, Event $event) - { - } - - /** - * Called after dispatching the event. - * - * @param string $eventName The event name - * @param Event $event The event - */ - protected function postDispatch($eventName, Event $event) - { - } - - private function wrapListener($eventName, $eventId, $listener) - { - $self = $this; - - return function (Event $event) use ($self, $eventName, $eventId, $listener) { - $e = $self->preListenerCall($eventName, $eventId, $listener); - - call_user_func($listener, $event, $eventName, $self); - - if ($e->isStarted()) { - $e->stop(); - } - - if ($event->isPropagationStopped()) { - $self->logSkippedListeners($eventName, $eventId, $event, $listener); - } - }; - } - - private function unwrapListener($listener, $eventId) - { - // get the original listener - if (is_object($listener)) { - if (null === $eventId) { - foreach (array_keys($this->wrappedListeners) as $eventId) { - if (isset($this->wrappedListeners[$eventId][$listener])) { - return $this->wrappedListeners[$eventId][$listener]; - } - } - } elseif (isset($this->wrappedListeners[$eventId][$listener])) { - return $this->wrappedListeners[$eventId][$listener]; - } - } - - return $listener; - } } diff --git a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php new file mode 100644 index 000000000000..c501662b07d1 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +class WrappedListener +{ + private $listener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + + public function __construct($listener, $name, Stopwatch $stopwatch) + { + $this->listener = $listener; + $this->name = $name; + $this->stopwatch = $stopwatch; + $this->called = false; + $this->stoppedPropagation = false; + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled() + { + return $this->called; + } + + public function stoppedPropagation() + { + return $this->stoppedPropagation; + } + + public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + { + $this->called = true; + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + call_user_func($this->listener, $event, $eventName, $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php index 0b4af59d59fa..399f1a788980 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -32,14 +32,10 @@ public function testStopwatchSections() $this->assertEquals(array( '__section__', 'kernel.request', - 'kernel.request.loading', 'kernel.controller', - 'kernel.controller.loading', 'controller', 'kernel.response', - 'kernel.response.loading', 'kernel.terminate', - 'kernel.terminate.loading', ), array_keys($events)); }