[EventDispatcher] Handle laziness internally instead of relying on ClosureProxyArgument
This commit is contained in:
parent
7a9875c3cd
commit
c17a009d66
@ -116,7 +116,7 @@ class ContainerAwareEventDispatcher extends EventDispatcher
|
|||||||
public function hasListeners($eventName = null)
|
public function hasListeners($eventName = null)
|
||||||
{
|
{
|
||||||
if (null === $eventName) {
|
if (null === $eventName) {
|
||||||
return (bool) count($this->listenerIds) || (bool) count($this->listeners);
|
return count($this->listenerIds) || count($this->listeners) || parent::hasListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->listenerIds[$eventName])) {
|
if (isset($this->listenerIds[$eventName])) {
|
||||||
|
@ -11,10 +11,11 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\EventDispatcher\DependencyInjection;
|
namespace Symfony\Component\EventDispatcher\DependencyInjection;
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
|
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ class RegisterListenersPass implements CompilerPassInterface
|
|||||||
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
|
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$definition->addMethodCall('addListener', array($event['event'], new ClosureProxyArgument($id, $event['method']), $priority));
|
$definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ class RegisterListenersPass implements CompilerPassInterface
|
|||||||
ExtractingEventDispatcher::$subscriber = $class;
|
ExtractingEventDispatcher::$subscriber = $class;
|
||||||
$extractingDispatcher->addSubscriber($extractingDispatcher);
|
$extractingDispatcher->addSubscriber($extractingDispatcher);
|
||||||
foreach ($extractingDispatcher->listeners as $args) {
|
foreach ($extractingDispatcher->listeners as $args) {
|
||||||
$args[1] = new ClosureProxyArgument($id, $args[1]);
|
$args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]);
|
||||||
$definition->addMethodCall('addListener', $args);
|
$definition->addMethodCall('addListener', $args);
|
||||||
}
|
}
|
||||||
$extractingDispatcher->listeners = array();
|
$extractingDispatcher->listeners = array();
|
||||||
|
@ -24,6 +24,7 @@ namespace Symfony\Component\EventDispatcher;
|
|||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
* @author Jordan Alliot <jordan.alliot@gmail.com>
|
* @author Jordan Alliot <jordan.alliot@gmail.com>
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
*/
|
*/
|
||||||
class EventDispatcher implements EventDispatcherInterface
|
class EventDispatcher implements EventDispatcherInterface
|
||||||
{
|
{
|
||||||
@ -52,7 +53,7 @@ class EventDispatcher implements EventDispatcherInterface
|
|||||||
public function getListeners($eventName = null)
|
public function getListeners($eventName = null)
|
||||||
{
|
{
|
||||||
if (null !== $eventName) {
|
if (null !== $eventName) {
|
||||||
if (!isset($this->listeners[$eventName])) {
|
if (empty($this->listeners[$eventName])) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +78,23 @@ class EventDispatcher implements EventDispatcherInterface
|
|||||||
*/
|
*/
|
||||||
public function getListenerPriority($eventName, $listener)
|
public function getListenerPriority($eventName, $listener)
|
||||||
{
|
{
|
||||||
if (!isset($this->listeners[$eventName])) {
|
if (empty($this->listeners[$eventName])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
|
||||||
|
$listener[0] = $listener[0]();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->listeners[$eventName] as $priority => $listeners) {
|
foreach ($this->listeners[$eventName] as $priority => $listeners) {
|
||||||
if (false !== in_array($listener, $listeners, true)) {
|
foreach ($listeners as $k => $v) {
|
||||||
return $priority;
|
if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
|
||||||
|
$v[0] = $v[0]();
|
||||||
|
$this->listeners[$eventName][$priority][$k] = $v;
|
||||||
|
}
|
||||||
|
if ($v === $listener) {
|
||||||
|
return $priority;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,7 +104,17 @@ class EventDispatcher implements EventDispatcherInterface
|
|||||||
*/
|
*/
|
||||||
public function hasListeners($eventName = null)
|
public function hasListeners($eventName = null)
|
||||||
{
|
{
|
||||||
return (bool) $this->getListeners($eventName);
|
if (null !== $eventName) {
|
||||||
|
return !empty($this->listeners[$eventName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->listeners as $eventListeners) {
|
||||||
|
if ($eventListeners) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,13 +131,30 @@ class EventDispatcher implements EventDispatcherInterface
|
|||||||
*/
|
*/
|
||||||
public function removeListener($eventName, $listener)
|
public function removeListener($eventName, $listener)
|
||||||
{
|
{
|
||||||
if (!isset($this->listeners[$eventName])) {
|
if (empty($this->listeners[$eventName])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
|
||||||
|
$listener[0] = $listener[0]();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->listeners[$eventName] as $priority => $listeners) {
|
foreach ($this->listeners[$eventName] as $priority => $listeners) {
|
||||||
if (false !== ($key = array_search($listener, $listeners, true))) {
|
foreach ($listeners as $k => $v) {
|
||||||
unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
|
if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
|
||||||
|
$v[0] = $v[0]();
|
||||||
|
}
|
||||||
|
if ($v === $listener) {
|
||||||
|
unset($listeners[$k], $this->sorted[$eventName]);
|
||||||
|
} else {
|
||||||
|
$listeners[$k] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($listeners) {
|
||||||
|
$this->listeners[$eventName][$priority] = $listeners;
|
||||||
|
} else {
|
||||||
|
unset($this->listeners[$eventName][$priority]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,6 +221,16 @@ class EventDispatcher implements EventDispatcherInterface
|
|||||||
private function sortListeners($eventName)
|
private function sortListeners($eventName)
|
||||||
{
|
{
|
||||||
krsort($this->listeners[$eventName]);
|
krsort($this->listeners[$eventName]);
|
||||||
$this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]);
|
$this->sorted[$eventName] = array();
|
||||||
|
|
||||||
|
foreach ($this->listeners[$eventName] as $priority => $listeners) {
|
||||||
|
foreach ($listeners as $k => $listener) {
|
||||||
|
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
|
||||||
|
$listener[0] = $listener[0]();
|
||||||
|
$this->listeners[$eventName][$priority][$k] = $listener;
|
||||||
|
}
|
||||||
|
$this->sorted[$eventName][] = $listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,6 +302,73 @@ abstract class AbstractEventDispatcherTest extends TestCase
|
|||||||
$this->assertFalse($this->dispatcher->hasListeners('foo'));
|
$this->assertFalse($this->dispatcher->hasListeners('foo'));
|
||||||
$this->assertFalse($this->dispatcher->hasListeners());
|
$this->assertFalse($this->dispatcher->hasListeners());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testHasListenersIsLazy()
|
||||||
|
{
|
||||||
|
$called = 0;
|
||||||
|
$listener = array(function () use (&$called) { ++$called; }, 'onFoo');
|
||||||
|
$this->dispatcher->addListener('foo', $listener);
|
||||||
|
$this->assertTrue($this->dispatcher->hasListeners());
|
||||||
|
$this->assertTrue($this->dispatcher->hasListeners('foo'));
|
||||||
|
$this->assertSame(0, $called);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDispatchLazyListener()
|
||||||
|
{
|
||||||
|
$called = 0;
|
||||||
|
$factory = function () use (&$called) {
|
||||||
|
++$called;
|
||||||
|
|
||||||
|
return new TestWithDispatcher();
|
||||||
|
};
|
||||||
|
$this->dispatcher->addListener('foo', array($factory, 'foo'));
|
||||||
|
$this->assertSame(0, $called);
|
||||||
|
$this->dispatcher->dispatch('foo', new Event());
|
||||||
|
$this->dispatcher->dispatch('foo', new Event());
|
||||||
|
$this->assertSame(1, $called);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRemoveFindsLazyListeners()
|
||||||
|
{
|
||||||
|
$test = new TestWithDispatcher();
|
||||||
|
$factory = function () use ($test) { return $test; };
|
||||||
|
|
||||||
|
$this->dispatcher->addListener('foo', array($factory, 'foo'));
|
||||||
|
$this->assertTrue($this->dispatcher->hasListeners('foo'));
|
||||||
|
$this->dispatcher->removeListener('foo', array($test, 'foo'));
|
||||||
|
$this->assertFalse($this->dispatcher->hasListeners('foo'));
|
||||||
|
|
||||||
|
$this->dispatcher->addListener('foo', array($test, 'foo'));
|
||||||
|
$this->assertTrue($this->dispatcher->hasListeners('foo'));
|
||||||
|
$this->dispatcher->removeListener('foo', array($factory, 'foo'));
|
||||||
|
$this->assertFalse($this->dispatcher->hasListeners('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPriorityFindsLazyListeners()
|
||||||
|
{
|
||||||
|
$test = new TestWithDispatcher();
|
||||||
|
$factory = function () use ($test) { return $test; };
|
||||||
|
|
||||||
|
$this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
|
||||||
|
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', array($test, 'foo')));
|
||||||
|
$this->dispatcher->removeListener('foo', array($factory, 'foo'));
|
||||||
|
|
||||||
|
$this->dispatcher->addListener('foo', array($test, 'foo'), 5);
|
||||||
|
$this->assertSame(5, $this->dispatcher->getListenerPriority('foo', array($factory, 'foo')));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetLazyListeners()
|
||||||
|
{
|
||||||
|
$test = new TestWithDispatcher();
|
||||||
|
$factory = function () use ($test) { return $test; };
|
||||||
|
|
||||||
|
$this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
|
||||||
|
$this->assertSame(array(array($test, 'foo')), $this->dispatcher->getListeners('foo'));
|
||||||
|
|
||||||
|
$this->dispatcher->removeListener('foo', array($test, 'foo'));
|
||||||
|
$this->dispatcher->addListener('bar', array($factory, 'foo'), 3);
|
||||||
|
$this->assertSame(array('bar' => array(array($test, 'foo'))), $this->dispatcher->getListeners());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CallableClass
|
class CallableClass
|
||||||
|
@ -12,8 +12,9 @@
|
|||||||
namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
|
namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
|
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
||||||
|
|
||||||
class RegisterListenersPassTest extends TestCase
|
class RegisterListenersPassTest extends TestCase
|
||||||
@ -127,17 +128,17 @@ class RegisterListenersPassTest extends TestCase
|
|||||||
$registerListenersPass->process($container);
|
$registerListenersPass->process($container);
|
||||||
|
|
||||||
$definition = $container->getDefinition('event_dispatcher');
|
$definition = $container->getDefinition('event_dispatcher');
|
||||||
$expected_calls = array(
|
$expectedCalls = array(
|
||||||
array(
|
array(
|
||||||
'addListener',
|
'addListener',
|
||||||
array(
|
array(
|
||||||
'event',
|
'event',
|
||||||
new ClosureProxyArgument('foo', 'onEvent'),
|
array(new ServiceClosureArgument(new Reference('foo')), 'onEvent'),
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$this->assertEquals($expected_calls, $definition->getMethodCalls());
|
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user