bug #14355 [2.6][EventDispatcher] make listeners removable from an executed listener (xabbuh)

This PR was merged into the 2.6 branch.

Discussion
----------

[2.6][EventDispatcher] make listeners removable from an executed listener

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

Commits
-------

f36803e [EventDispatcher] make listeners removable from an executed listener
This commit is contained in:
Fabien Potencier 2015-04-19 19:01:19 +02:00
commit f48cc1ba34
2 changed files with 31 additions and 2 deletions

View File

@ -31,6 +31,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
private $called; private $called;
private $dispatcher; private $dispatcher;
private $wrappedListeners;
/** /**
* Constructor. * Constructor.
@ -45,6 +46,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->stopwatch = $stopwatch; $this->stopwatch = $stopwatch;
$this->logger = $logger; $this->logger = $logger;
$this->called = array(); $this->called = array();
$this->wrappedListeners = array();
} }
/** /**
@ -68,6 +70,16 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
*/ */
public function removeListener($eventName, $listener) public function removeListener($eventName, $listener)
{ {
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
$listener = $wrappedListener;
unset($this->wrappedListeners[$eventName][$index]);
break;
}
}
}
return $this->dispatcher->removeListener($eventName, $listener); return $this->dispatcher->removeListener($eventName, $listener);
} }
@ -216,12 +228,15 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->removeListener($eventName, $listener);
$info = $this->getListenerInfo($listener, $eventName); $info = $this->getListenerInfo($listener, $eventName);
$name = isset($info['class']) ? $info['class'] : $info['type']; $name = isset($info['class']) ? $info['class'] : $info['type'];
$this->dispatcher->addListener($eventName, new WrappedListener($listener, $name, $this->stopwatch, $this)); $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->addListener($eventName, $wrappedListener);
} }
} }
private function postProcess($eventName) private function postProcess($eventName)
{ {
unset($this->wrappedListeners[$eventName]);
$skipped = false; $skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) { foreach ($this->dispatcher->getListeners($eventName) as $listener) {
if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
@ -259,7 +274,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
} }
/** /**
* Returns information about the listener * Returns information about the listener.
* *
* @param object $listener The listener * @param object $listener The listener
* @param string $eventName The event name * @param string $eventName The event name

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\EventDispatcher\Tests\Debug; namespace Symfony\Component\EventDispatcher\Tests\Debug;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\Event;
@ -174,6 +175,19 @@ class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase
$dispatcher->dispatch('foo'); $dispatcher->dispatch('foo');
$this->assertTrue($nestedCall); $this->assertTrue($nestedCall);
} }
public function testListenerCanRemoveItselfWhenExecuted()
{
$eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
$dispatcher->removeListener('foo', $listener1);
};
$eventDispatcher->addListener('foo', $listener1);
$eventDispatcher->addListener('foo', function () {});
$eventDispatcher->dispatch('foo');
$this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
}
} }
class EventSubscriber implements EventSubscriberInterface class EventSubscriber implements EventSubscriberInterface