diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index 66b99ecf62065..2a3e563377c58 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -28,7 +28,16 @@ class ContainerAwareEventManager extends EventManager * => */ private $listeners = []; + + /** + * Map of initialized listener hash per listener service id. + * + * => + */ + private $initializedHashes = []; + private $initialized = []; + private $container; public function __construct(ContainerInterface $container) @@ -43,24 +52,21 @@ public function __construct(ContainerInterface $container) * the name of the method that is invoked on listeners. * @param EventArgs $eventArgs The event arguments to pass to the event handlers/listeners. * If not supplied, the single empty EventArgs instance is used. - * - * @return bool */ public function dispatchEvent($eventName, EventArgs $eventArgs = null) { - if (isset($this->listeners[$eventName])) { - $eventArgs = null === $eventArgs ? EventArgs::getEmptyInstance() : $eventArgs; + if (!isset($this->listeners[$eventName])) { + return; + } - $initialized = isset($this->initialized[$eventName]); + $eventArgs = null === $eventArgs ? EventArgs::getEmptyInstance() : $eventArgs; - foreach ($this->listeners[$eventName] as $hash => $listener) { - if (!$initialized && \is_string($listener)) { - $this->listeners[$eventName][$hash] = $listener = $this->container->get($listener); - } + if (!isset($this->initialized[$eventName])) { + $this->initializeListeners($eventName); + } - $listener->$eventName($eventArgs); - } - $this->initialized[$eventName] = true; + foreach ($this->listeners[$eventName] as $hash => $listener) { + $listener->$eventName($eventArgs); } } @@ -73,7 +79,21 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null) */ public function getListeners($event = null) { - return $event ? $this->listeners[$event] : $this->listeners; + if (null !== $event) { + if (!isset($this->initialized[$event])) { + $this->initializeListeners($event); + } + + return $this->listeners[$event]; + } + + foreach (array_keys($this->listeners) as $event) { + if (!isset($this->initialized[$event])) { + $this->initializeListeners($event); + } + } + + return $this->listeners; } /** @@ -99,10 +119,6 @@ public function hasListeners($event) public function addEventListener($events, $listener) { if (\is_string($listener)) { - if ($this->initialized) { - throw new \RuntimeException('Adding lazy-loading listeners after construction is not supported.'); - } - $hash = '_service_'.$listener; } else { // Picks the hash code related to that listener @@ -113,6 +129,10 @@ public function addEventListener($events, $listener) // Overrides listener if a previous one was associated already // Prevents duplicate listeners on same event (same instance only) $this->listeners[$event][$hash] = $listener; + + if (\is_string($listener)) { + unset($this->initialized[$event]); + } } } @@ -126,6 +146,10 @@ public function removeEventListener($events, $listener) { if (\is_string($listener)) { $hash = '_service_'.$listener; + // Service already initialized + if (isset($this->initializedHashes[$hash])) { + $hash = $this->initializedHashes[$hash]; + } } else { // Picks the hash code related to that listener $hash = spl_object_hash($listener); @@ -138,4 +162,19 @@ public function removeEventListener($events, $listener) } } } + + /** + * @param string $eventName + */ + private function initializeListeners($eventName) + { + foreach ($this->listeners[$eventName] as $hash => $listener) { + if (\is_string($listener)) { + $listener = $this->container->get($listener); + $this->listeners[$eventName][$hash] = $listener; + $this->initializedHashes[$hash] = spl_object_hash($listener); + } + } + $this->initialized[$eventName] = true; + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php index 2e97edb1d2e14..17579473c1c10 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php @@ -28,8 +28,8 @@ protected function setUp() public function testDispatchEvent() { - $this->container->set('foobar', $listener1 = new MyListener()); - $this->evm->addEventListener('foo', 'foobar'); + $this->container->set('lazy', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy'); $this->evm->addEventListener('foo', $listener2 = new MyListener()); $this->evm->dispatchEvent('foo'); @@ -38,19 +38,67 @@ public function testDispatchEvent() $this->assertTrue($listener2->called); } + public function testAddEventListenerAfterDispatchEvent() + { + $this->container->set('lazy1', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy1'); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); + + $this->evm->dispatchEvent('foo'); + + $this->container->set('lazy2', $listener3 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy2'); + $this->evm->addEventListener('foo', $listener4 = new MyListener()); + + $this->evm->dispatchEvent('foo'); + + $this->assertTrue($listener3->called); + $this->assertTrue($listener4->called); + } + + public function testGetListenersForEvent() + { + $this->container->set('lazy', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy'); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); + + $this->assertSame([$listener1, $listener2], array_values($this->evm->getListeners('foo'))); + } + + public function testGetListeners() + { + $this->container->set('lazy', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy'); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); + + $this->assertSame([$listener1, $listener2], array_values($this->evm->getListeners()['foo'])); + } + public function testRemoveEventListener() { - $this->evm->addEventListener('foo', 'bar'); - $this->evm->addEventListener('foo', $listener = new MyListener()); + $this->container->set('lazy', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy'); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); + + $this->evm->removeEventListener('foo', $listener2); + $this->assertSame([$listener1], $this->evm->getListeners('foo')); + + $this->evm->removeEventListener('foo', 'lazy'); + $this->assertSame([], $this->evm->getListeners('foo')); + } + + public function testRemoveEventListenerAfterDispatchEvent() + { + $this->container->set('lazy', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy'); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); - $listeners = ['foo' => ['_service_bar' => 'bar', spl_object_hash($listener) => $listener]]; - $this->assertSame($listeners, $this->evm->getListeners()); - $this->assertSame($listeners['foo'], $this->evm->getListeners('foo')); + $this->evm->dispatchEvent('foo'); - $this->evm->removeEventListener('foo', $listener); - $this->assertSame(['_service_bar' => 'bar'], $this->evm->getListeners('foo')); + $this->evm->removeEventListener('foo', $listener2); + $this->assertSame([$listener1], $this->evm->getListeners('foo')); - $this->evm->removeEventListener('foo', 'bar'); + $this->evm->removeEventListener('foo', 'lazy'); $this->assertSame([], $this->evm->getListeners('foo')); } }