Skip to content

Commit

Permalink
feature #10200 [EventDispatcher] simplified code for TraceableEventDi…
Browse files Browse the repository at this point in the history
…spatcher (fabpot)

This PR was merged into the 2.5-dev branch.

Discussion
----------

[EventDispatcher] simplified code for TraceableEventDispatcher

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

Commits
-------

42e4c7b [EventDispatcher] simplified code for TraceableEventDispatcher
  • Loading branch information
fabpot committed Feb 6, 2014
2 parents 6dfdb97 + 42e4c7b commit fe86efd
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 147 deletions.
220 changes: 77 additions & 143 deletions src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php
Expand Up @@ -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.
Expand All @@ -46,6 +44,7 @@ public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $sto
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->called = array();
}

/**
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}

/**
Expand All @@ -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;
}
}
}
Expand All @@ -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;
}
}
}

/**
Expand All @@ -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,
);
Expand Down Expand Up @@ -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;
}
}
69 changes: 69 additions & 0 deletions src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php
@@ -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;
}
}
}
Expand Up @@ -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));
}

Expand Down

0 comments on commit fe86efd

Please sign in to comment.