[EventDispatcher] simplified code for TraceableEventDispatcher

This commit is contained in:
Fabien Potencier 2014-02-04 15:06:59 +01:00
parent 22970e007e
commit 42e4c7bddf
3 changed files with 147 additions and 148 deletions

View File

@ -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 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->called = array();
}
/**
@ -105,47 +104,19 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$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 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
*/
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 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
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 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
}
/**
* 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]->attach($listener);
}
if (null !== $this->logger && $skipped) {
$this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
}
if ($listener->stoppedPropagation()) {
if (null !== $this->logger) {
$this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
}
$skipped = true;
}
}
$this->called[$eventName.'.'.$info['pretty']] = $info;
return $this->stopwatch->start(isset($info['class']) ? $info['class'] : $info['type'], 'event_listener');
}
/**
@ -259,10 +252,8 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
*
* @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 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
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;
}
}

View File

@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <fabien@symfony.com>
*/
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;
}
}
}

View File

@ -32,14 +32,10 @@ class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase
$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));
}