Skip to content

[DoctrineBridge][EventDispatcher] Add LazyEventManager and LazyEventDispatcher #20039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 6 additions & 120 deletions src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,133 +11,19 @@

namespace Symfony\Bridge\Doctrine;

use Doctrine\Common\EventArgs;
use Doctrine\Common\EventManager;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Allows lazy loading of listener services.
* Lazily loads listeners from the dependency injection container.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Jáchym Toušek <enumag@gmail.com>
*/
class ContainerAwareEventManager extends EventManager
class ContainerAwareEventManager extends LazyEventManager
{
/**
* Map of registered listeners.
*
* <event> => <listeners>
*
* @var array
*/
private $listeners = array();
private $initialized = array();
private $container;

public function __construct(ContainerInterface $container)
{
$this->container = $container;
}

/**
* Dispatches an event to all registered listeners.
*
* @param string $eventName The name of the event to dispatch. The name of the event is
* 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;

$initialized = isset($this->initialized[$eventName]);

foreach ($this->listeners[$eventName] as $hash => $listener) {
if (!$initialized && is_string($listener)) {
$this->listeners[$eventName][$hash] = $listener = $this->container->get($listener);
}

$listener->$eventName($eventArgs);
}
$this->initialized[$eventName] = true;
}
}

/**
* Gets the listeners of a specific event or all listeners.
*
* @param string $event The name of the event
*
* @return array The event listeners for the specified event, or all event listeners
*/
public function getListeners($event = null)
{
return $event ? $this->listeners[$event] : $this->listeners;
}

/**
* Checks whether an event has any registered listeners.
*
* @param string $event
*
* @return bool TRUE if the specified event has any listeners, FALSE otherwise
*/
public function hasListeners($event)
{
return isset($this->listeners[$event]) && $this->listeners[$event];
}

/**
* Adds an event listener that listens on the specified events.
*
* @param string|array $events The event(s) to listen on
* @param object|string $listener The listener object
*
* @throws \RuntimeException
*/
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
$hash = spl_object_hash($listener);
}

foreach ((array) $events as $event) {
// Overrides listener if a previous one was associated already
// Prevents duplicate listeners on same event (same instance only)
$this->listeners[$event][$hash] = $listener;
}
}

/**
* Removes an event listener from the specified events.
*
* @param string|array $events
* @param object|string $listener
*/
public function removeEventListener($events, $listener)
{
if (is_string($listener)) {
$hash = '_service_'.$listener;
} else {
// Picks the hash code related to that listener
$hash = spl_object_hash($listener);
}

foreach ((array) $events as $event) {
// Check if actually have this listener associated
if (isset($this->listeners[$event][$hash])) {
unset($this->listeners[$event][$hash]);
}
}
parent::__construct(function ($serviceId) use ($container) {
return $container->get($serviceId);
});
}
}
154 changes: 154 additions & 0 deletions src/Symfony/Bridge/Doctrine/LazyEventManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?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\Bridge\Doctrine;

use Doctrine\Common\EventArgs;
use Doctrine\Common\EventManager;

/**
* Allows lazy loading of listeners.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class LazyEventManager extends EventManager
{
/**
* Map of registered listeners.
*
* <event> => <listeners>
*
* @var array
*/
private $listeners = array();
private $initialized = array();

/**
* The resolver used for lazy loading.
*
* @var callable
*/
private $listenerResolver;

/**
* Constructor.
*
* @param callable $listenerResolver The resolver receives a string identifier of the listener
* and should return an instance of EventSubscriber.
*/
public function __construct(callable $listenerResolver)
{
$this->listenerResolver = $listenerResolver;
}

/**
* Dispatches an event to all registered listeners.
*
* @param string $eventName The name of the event to dispatch. The name of the event is
* 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;

$initialized = isset($this->initialized[$eventName]);

foreach ($this->listeners[$eventName] as $hash => $listener) {
if (!$initialized && is_string($listener)) {
$this->listeners[$eventName][$hash] = $listener = call_user_func($this->listenerResolver, $listener);
}

$listener->$eventName($eventArgs);
}
$this->initialized[$eventName] = true;
}
}

/**
* Gets the listeners of a specific event or all listeners.
*
* @param string $event The name of the event
*
* @return array The event listeners for the specified event, or all event listeners
*/
public function getListeners($event = null)
{
return $event ? $this->listeners[$event] : $this->listeners;
}

/**
* Checks whether an event has any registered listeners.
*
* @param string $event
*
* @return bool TRUE if the specified event has any listeners, FALSE otherwise
*/
public function hasListeners($event)
{
return isset($this->listeners[$event]) && $this->listeners[$event];
}

/**
* Adds an event listener that listens on the specified events.
*
* @param string|array $events The event(s) to listen on
* @param object|string $listener The listener object
*
* @throws \RuntimeException
*/
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
$hash = spl_object_hash($listener);
}

foreach ((array) $events as $event) {
// Overrides listener if a previous one was associated already
// Prevents duplicate listeners on same event (same instance only)
$this->listeners[$event][$hash] = $listener;
}
}

/**
* Removes an event listener from the specified events.
*
* @param string|array $events
* @param object|string $listener
*/
public function removeEventListener($events, $listener)
{
if (is_string($listener)) {
$hash = '_service_'.$listener;
} else {
// Picks the hash code related to that listener
$hash = spl_object_hash($listener);
}

foreach ((array) $events as $event) {
// Check if actually have this listener associated
if (isset($this->listeners[$event][$hash])) {
unset($this->listeners[$event][$hash]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,16 @@
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
use Symfony\Component\DependencyInjection\Container;

class ContainerAwareEventManagerTest extends \PHPUnit_Framework_TestCase
class ContainerAwareEventManagerTest extends LazyEventManagerTest
{
private $container;
private $evm;

protected function setUp()
{
$this->container = new Container();
$this->evm = new ContainerAwareEventManager($this->container);
}

public function testDispatchEvent()
{
$this->container->set('foobar', $listener1 = new MyListener());
$this->evm->addEventListener('foo', 'foobar');
$this->evm->addEventListener('foo', $listener2 = new MyListener());

$this->evm->dispatchEvent('foo');

$this->assertTrue($listener1->called);
$this->assertTrue($listener2->called);
}

public function testRemoveEventListener()
protected function createEventManager($id = null, $listener = null)
{
$this->evm->addEventListener('foo', 'bar');
$this->evm->addEventListener('foo', $listener = new MyListener());

$listeners = array('foo' => array('_service_bar' => 'bar', spl_object_hash($listener) => $listener));
$this->assertSame($listeners, $this->evm->getListeners());
$this->assertSame($listeners['foo'], $this->evm->getListeners('foo'));

$this->evm->removeEventListener('foo', $listener);
$this->assertSame(array('_service_bar' => 'bar'), $this->evm->getListeners('foo'));

$this->evm->removeEventListener('foo', 'bar');
$this->assertSame(array(), $this->evm->getListeners('foo'));
}
}
$container = new Container();

class MyListener
{
public $called = false;
if ($id && $listener) {
$container->set($id, $listener);
}

public function foo()
{
$this->called = true;
return new ContainerAwareEventManager($container);
}
}
Loading