Skip to content

Commit c17a009

Browse files
[EventDispatcher] Handle laziness internally instead of relying on ClosureProxyArgument
1 parent 7a9875c commit c17a009

File tree

5 files changed

+134
-17
lines changed

5 files changed

+134
-17
lines changed

src/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public function removeListener($eventName, $listener)
116116
public function hasListeners($eventName = null)
117117
{
118118
if (null === $eventName) {
119-
return (bool) count($this->listenerIds) || (bool) count($this->listeners);
119+
return count($this->listenerIds) || count($this->listeners) || parent::hasListeners();
120120
}
121121

122122
if (isset($this->listenerIds[$eventName])) {

src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111

1212
namespace Symfony\Component\EventDispatcher\DependencyInjection;
1313

14-
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
14+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1717
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
18+
use Symfony\Component\DependencyInjection\Reference;
1819
use Symfony\Component\EventDispatcher\EventDispatcher;
1920
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2021

@@ -78,7 +79,7 @@ public function process(ContainerBuilder $container)
7879
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
7980
}
8081

81-
$definition->addMethodCall('addListener', array($event['event'], new ClosureProxyArgument($id, $event['method']), $priority));
82+
$definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority));
8283
}
8384
}
8485

@@ -103,7 +104,7 @@ public function process(ContainerBuilder $container)
103104
ExtractingEventDispatcher::$subscriber = $class;
104105
$extractingDispatcher->addSubscriber($extractingDispatcher);
105106
foreach ($extractingDispatcher->listeners as $args) {
106-
$args[1] = new ClosureProxyArgument($id, $args[1]);
107+
$args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]);
107108
$definition->addMethodCall('addListener', $args);
108109
}
109110
$extractingDispatcher->listeners = array();

src/Symfony/Component/EventDispatcher/EventDispatcher.php

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* @author Fabien Potencier <fabien@symfony.com>
2525
* @author Jordi Boggiano <j.boggiano@seld.be>
2626
* @author Jordan Alliot <jordan.alliot@gmail.com>
27+
* @author Nicolas Grekas <p@tchwork.com>
2728
*/
2829
class EventDispatcher implements EventDispatcherInterface
2930
{
@@ -52,7 +53,7 @@ public function dispatch($eventName, Event $event = null)
5253
public function getListeners($eventName = null)
5354
{
5455
if (null !== $eventName) {
55-
if (!isset($this->listeners[$eventName])) {
56+
if (empty($this->listeners[$eventName])) {
5657
return array();
5758
}
5859

@@ -77,13 +78,23 @@ public function getListeners($eventName = null)
7778
*/
7879
public function getListenerPriority($eventName, $listener)
7980
{
80-
if (!isset($this->listeners[$eventName])) {
81+
if (empty($this->listeners[$eventName])) {
8182
return;
8283
}
8384

85+
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
86+
$listener[0] = $listener[0]();
87+
}
88+
8489
foreach ($this->listeners[$eventName] as $priority => $listeners) {
85-
if (false !== in_array($listener, $listeners, true)) {
86-
return $priority;
90+
foreach ($listeners as $k => $v) {
91+
if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
92+
$v[0] = $v[0]();
93+
$this->listeners[$eventName][$priority][$k] = $v;
94+
}
95+
if ($v === $listener) {
96+
return $priority;
97+
}
8798
}
8899
}
89100
}
@@ -93,7 +104,17 @@ public function getListenerPriority($eventName, $listener)
93104
*/
94105
public function hasListeners($eventName = null)
95106
{
96-
return (bool) $this->getListeners($eventName);
107+
if (null !== $eventName) {
108+
return !empty($this->listeners[$eventName]);
109+
}
110+
111+
foreach ($this->listeners as $eventListeners) {
112+
if ($eventListeners) {
113+
return true;
114+
}
115+
}
116+
117+
return false;
97118
}
98119

99120
/**
@@ -110,13 +131,30 @@ public function addListener($eventName, $listener, $priority = 0)
110131
*/
111132
public function removeListener($eventName, $listener)
112133
{
113-
if (!isset($this->listeners[$eventName])) {
134+
if (empty($this->listeners[$eventName])) {
114135
return;
115136
}
116137

138+
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
139+
$listener[0] = $listener[0]();
140+
}
141+
117142
foreach ($this->listeners[$eventName] as $priority => $listeners) {
118-
if (false !== ($key = array_search($listener, $listeners, true))) {
119-
unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
143+
foreach ($listeners as $k => $v) {
144+
if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
145+
$v[0] = $v[0]();
146+
}
147+
if ($v === $listener) {
148+
unset($listeners[$k], $this->sorted[$eventName]);
149+
} else {
150+
$listeners[$k] = $v;
151+
}
152+
}
153+
154+
if ($listeners) {
155+
$this->listeners[$eventName][$priority] = $listeners;
156+
} else {
157+
unset($this->listeners[$eventName][$priority]);
120158
}
121159
}
122160
}
@@ -183,6 +221,16 @@ protected function doDispatch($listeners, $eventName, Event $event)
183221
private function sortListeners($eventName)
184222
{
185223
krsort($this->listeners[$eventName]);
186-
$this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]);
224+
$this->sorted[$eventName] = array();
225+
226+
foreach ($this->listeners[$eventName] as $priority => $listeners) {
227+
foreach ($listeners as $k => $listener) {
228+
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
229+
$listener[0] = $listener[0]();
230+
$this->listeners[$eventName][$priority][$k] = $listener;
231+
}
232+
$this->sorted[$eventName][] = $listener;
233+
}
234+
}
187235
}
188236
}

src/Symfony/Component/EventDispatcher/Tests/AbstractEventDispatcherTest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,73 @@ public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEv
302302
$this->assertFalse($this->dispatcher->hasListeners('foo'));
303303
$this->assertFalse($this->dispatcher->hasListeners());
304304
}
305+
306+
public function testHasListenersIsLazy()
307+
{
308+
$called = 0;
309+
$listener = array(function () use (&$called) { ++$called; }, 'onFoo');
310+
$this->dispatcher->addListener('foo', $listener);
311+
$this->assertTrue($this->dispatcher->hasListeners());
312+
$this->assertTrue($this->dispatcher->hasListeners('foo'));
313+
$this->assertSame(0, $called);
314+
}
315+
316+
public function testDispatchLazyListener()
317+
{
318+
$called = 0;
319+
$factory = function () use (&$called) {
320+
++$called;
321+
322+
return new TestWithDispatcher();
323+
};
324+
$this->dispatcher->addListener('foo', array($factory, 'foo'));
325+
$this->assertSame(0, $called);
326+
$this->dispatcher->dispatch('foo', new Event());
327+
$this->dispatcher->dispatch('foo', new Event());
328+
$this->assertSame(1, $called);
329+
}
330+
331+
public function testRemoveFindsLazyListeners()
332+
{
333+
$test = new TestWithDispatcher();
334+
$factory = function () use ($test) { return $test; };
335+
336+
$this->dispatcher->addListener('foo', array($factory, 'foo'));
337+
$this->assertTrue($this->dispatcher->hasListeners('foo'));
338+
$this->dispatcher->removeListener('foo', array($test, 'foo'));
339+
$this->assertFalse($this->dispatcher->hasListeners('foo'));
340+
341+
$this->dispatcher->addListener('foo', array($test, 'foo'));
342+
$this->assertTrue($this->dispatcher->hasListeners('foo'));
343+
$this->dispatcher->removeListener('foo', array($factory, 'foo'));
344+
$this->assertFalse($this->dispatcher->hasListeners('foo'));
345+
}
346+
347+
public function testPriorityFindsLazyListeners()
348+
{
349+
$test = new TestWithDispatcher();
350+
$factory = function () use ($test) { return $test; };
351+
352+
$this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
353+
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', array($test, 'foo')));
354+
$this->dispatcher->removeListener('foo', array($factory, 'foo'));
355+
356+
$this->dispatcher->addListener('foo', array($test, 'foo'), 5);
357+
$this->assertSame(5, $this->dispatcher->getListenerPriority('foo', array($factory, 'foo')));
358+
}
359+
360+
public function testGetLazyListeners()
361+
{
362+
$test = new TestWithDispatcher();
363+
$factory = function () use ($test) { return $test; };
364+
365+
$this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
366+
$this->assertSame(array(array($test, 'foo')), $this->dispatcher->getListeners('foo'));
367+
368+
$this->dispatcher->removeListener('foo', array($test, 'foo'));
369+
$this->dispatcher->addListener('bar', array($factory, 'foo'), 3);
370+
$this->assertSame(array('bar' => array(array($test, 'foo'))), $this->dispatcher->getListeners());
371+
}
305372
}
306373

307374
class CallableClass

src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
1313

1414
use PHPUnit\Framework\TestCase;
15-
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
1718
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
1819

1920
class RegisterListenersPassTest extends TestCase
@@ -127,17 +128,17 @@ public function testEventSubscriberResolvableClassName()
127128
$registerListenersPass->process($container);
128129

129130
$definition = $container->getDefinition('event_dispatcher');
130-
$expected_calls = array(
131+
$expectedCalls = array(
131132
array(
132133
'addListener',
133134
array(
134135
'event',
135-
new ClosureProxyArgument('foo', 'onEvent'),
136+
array(new ServiceClosureArgument(new Reference('foo')), 'onEvent'),
136137
0,
137138
),
138139
),
139140
);
140-
$this->assertEquals($expected_calls, $definition->getMethodCalls());
141+
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
141142
}
142143

143144
/**

0 commit comments

Comments
 (0)