1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\EventDispatcher\Tests;
13
14use PHPUnit\Framework\TestCase;
15use Symfony\Component\EventDispatcher\Event;
16use Symfony\Component\EventDispatcher\EventDispatcher;
17use Symfony\Component\EventDispatcher\EventSubscriberInterface;
18
19abstract class AbstractEventDispatcherTest extends TestCase
20{
21    /* Some pseudo events */
22    const preFoo = 'pre.foo';
23    const postFoo = 'post.foo';
24    const preBar = 'pre.bar';
25    const postBar = 'post.bar';
26
27    /**
28     * @var EventDispatcher
29     */
30    private $dispatcher;
31
32    private $listener;
33
34    protected function setUp()
35    {
36        $this->dispatcher = $this->createEventDispatcher();
37        $this->listener = new TestEventListener();
38    }
39
40    protected function tearDown()
41    {
42        $this->dispatcher = null;
43        $this->listener = null;
44    }
45
46    abstract protected function createEventDispatcher();
47
48    public function testInitialState()
49    {
50        $this->assertEquals([], $this->dispatcher->getListeners());
51        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
52        $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
53    }
54
55    public function testAddListener()
56    {
57        $this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
58        $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
59        $this->assertTrue($this->dispatcher->hasListeners());
60        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
61        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
62        $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
63        $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
64        $this->assertCount(2, $this->dispatcher->getListeners());
65    }
66
67    public function testGetListenersSortsByPriority()
68    {
69        $listener1 = new TestEventListener();
70        $listener2 = new TestEventListener();
71        $listener3 = new TestEventListener();
72        $listener1->name = '1';
73        $listener2->name = '2';
74        $listener3->name = '3';
75
76        $this->dispatcher->addListener('pre.foo', [$listener1, 'preFoo'], -10);
77        $this->dispatcher->addListener('pre.foo', [$listener2, 'preFoo'], 10);
78        $this->dispatcher->addListener('pre.foo', [$listener3, 'preFoo']);
79
80        $expected = [
81            [$listener2, 'preFoo'],
82            [$listener3, 'preFoo'],
83            [$listener1, 'preFoo'],
84        ];
85
86        $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
87    }
88
89    public function testGetAllListenersSortsByPriority()
90    {
91        $listener1 = new TestEventListener();
92        $listener2 = new TestEventListener();
93        $listener3 = new TestEventListener();
94        $listener4 = new TestEventListener();
95        $listener5 = new TestEventListener();
96        $listener6 = new TestEventListener();
97
98        $this->dispatcher->addListener('pre.foo', $listener1, -10);
99        $this->dispatcher->addListener('pre.foo', $listener2);
100        $this->dispatcher->addListener('pre.foo', $listener3, 10);
101        $this->dispatcher->addListener('post.foo', $listener4, -10);
102        $this->dispatcher->addListener('post.foo', $listener5);
103        $this->dispatcher->addListener('post.foo', $listener6, 10);
104
105        $expected = [
106            'pre.foo' => [$listener3, $listener2, $listener1],
107            'post.foo' => [$listener6, $listener5, $listener4],
108        ];
109
110        $this->assertSame($expected, $this->dispatcher->getListeners());
111    }
112
113    public function testGetListenerPriority()
114    {
115        $listener1 = new TestEventListener();
116        $listener2 = new TestEventListener();
117
118        $this->dispatcher->addListener('pre.foo', $listener1, -10);
119        $this->dispatcher->addListener('pre.foo', $listener2);
120
121        $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
122        $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
123        $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
124        $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {}));
125    }
126
127    public function testDispatch()
128    {
129        $this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
130        $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
131        $this->dispatcher->dispatch(self::preFoo);
132        $this->assertTrue($this->listener->preFooInvoked);
133        $this->assertFalse($this->listener->postFooInvoked);
134        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent'));
135        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
136        $event = new Event();
137        $return = $this->dispatcher->dispatch(self::preFoo, $event);
138        $this->assertSame($event, $return);
139    }
140
141    public function testDispatchForClosure()
142    {
143        $invoked = 0;
144        $listener = function () use (&$invoked) {
145            ++$invoked;
146        };
147        $this->dispatcher->addListener('pre.foo', $listener);
148        $this->dispatcher->addListener('post.foo', $listener);
149        $this->dispatcher->dispatch(self::preFoo);
150        $this->assertEquals(1, $invoked);
151    }
152
153    public function testStopEventPropagation()
154    {
155        $otherListener = new TestEventListener();
156
157        // postFoo() stops the propagation, so only one listener should
158        // be executed
159        // Manually set priority to enforce $this->listener to be called first
160        $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo'], 10);
161        $this->dispatcher->addListener('post.foo', [$otherListener, 'postFoo']);
162        $this->dispatcher->dispatch(self::postFoo);
163        $this->assertTrue($this->listener->postFooInvoked);
164        $this->assertFalse($otherListener->postFooInvoked);
165    }
166
167    public function testDispatchByPriority()
168    {
169        $invoked = [];
170        $listener1 = function () use (&$invoked) {
171            $invoked[] = '1';
172        };
173        $listener2 = function () use (&$invoked) {
174            $invoked[] = '2';
175        };
176        $listener3 = function () use (&$invoked) {
177            $invoked[] = '3';
178        };
179        $this->dispatcher->addListener('pre.foo', $listener1, -10);
180        $this->dispatcher->addListener('pre.foo', $listener2);
181        $this->dispatcher->addListener('pre.foo', $listener3, 10);
182        $this->dispatcher->dispatch(self::preFoo);
183        $this->assertEquals(['3', '2', '1'], $invoked);
184    }
185
186    public function testRemoveListener()
187    {
188        $this->dispatcher->addListener('pre.bar', $this->listener);
189        $this->assertTrue($this->dispatcher->hasListeners(self::preBar));
190        $this->dispatcher->removeListener('pre.bar', $this->listener);
191        $this->assertFalse($this->dispatcher->hasListeners(self::preBar));
192        $this->dispatcher->removeListener('notExists', $this->listener);
193    }
194
195    public function testAddSubscriber()
196    {
197        $eventSubscriber = new TestEventSubscriber();
198        $this->dispatcher->addSubscriber($eventSubscriber);
199        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
200        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
201    }
202
203    public function testAddSubscriberWithPriorities()
204    {
205        $eventSubscriber = new TestEventSubscriber();
206        $this->dispatcher->addSubscriber($eventSubscriber);
207
208        $eventSubscriber = new TestEventSubscriberWithPriorities();
209        $this->dispatcher->addSubscriber($eventSubscriber);
210
211        $listeners = $this->dispatcher->getListeners('pre.foo');
212        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
213        $this->assertCount(2, $listeners);
214        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
215    }
216
217    public function testAddSubscriberWithMultipleListeners()
218    {
219        $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
220        $this->dispatcher->addSubscriber($eventSubscriber);
221
222        $listeners = $this->dispatcher->getListeners('pre.foo');
223        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
224        $this->assertCount(2, $listeners);
225        $this->assertEquals('preFoo2', $listeners[0][1]);
226    }
227
228    public function testRemoveSubscriber()
229    {
230        $eventSubscriber = new TestEventSubscriber();
231        $this->dispatcher->addSubscriber($eventSubscriber);
232        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
233        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
234        $this->dispatcher->removeSubscriber($eventSubscriber);
235        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
236        $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
237    }
238
239    public function testRemoveSubscriberWithPriorities()
240    {
241        $eventSubscriber = new TestEventSubscriberWithPriorities();
242        $this->dispatcher->addSubscriber($eventSubscriber);
243        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
244        $this->dispatcher->removeSubscriber($eventSubscriber);
245        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
246    }
247
248    public function testRemoveSubscriberWithMultipleListeners()
249    {
250        $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
251        $this->dispatcher->addSubscriber($eventSubscriber);
252        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
253        $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
254        $this->dispatcher->removeSubscriber($eventSubscriber);
255        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
256    }
257
258    public function testEventReceivesTheDispatcherInstanceAsArgument()
259    {
260        $listener = new TestWithDispatcher();
261        $this->dispatcher->addListener('test', [$listener, 'foo']);
262        $this->assertNull($listener->name);
263        $this->assertNull($listener->dispatcher);
264        $this->dispatcher->dispatch('test');
265        $this->assertEquals('test', $listener->name);
266        $this->assertSame($this->dispatcher, $listener->dispatcher);
267    }
268
269    /**
270     * @see https://bugs.php.net/62976
271     *
272     * This bug affects:
273     *  - The PHP 5.3 branch for versions < 5.3.18
274     *  - The PHP 5.4 branch for versions < 5.4.8
275     *  - The PHP 5.5 branch is not affected
276     */
277    public function testWorkaroundForPhpBug62976()
278    {
279        $dispatcher = $this->createEventDispatcher();
280        $dispatcher->addListener('bug.62976', new CallableClass());
281        $dispatcher->removeListener('bug.62976', function () {});
282        $this->assertTrue($dispatcher->hasListeners('bug.62976'));
283    }
284
285    public function testHasListenersWhenAddedCallbackListenerIsRemoved()
286    {
287        $listener = function () {};
288        $this->dispatcher->addListener('foo', $listener);
289        $this->dispatcher->removeListener('foo', $listener);
290        $this->assertFalse($this->dispatcher->hasListeners());
291    }
292
293    public function testGetListenersWhenAddedCallbackListenerIsRemoved()
294    {
295        $listener = function () {};
296        $this->dispatcher->addListener('foo', $listener);
297        $this->dispatcher->removeListener('foo', $listener);
298        $this->assertSame([], $this->dispatcher->getListeners());
299    }
300
301    public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
302    {
303        $this->assertFalse($this->dispatcher->hasListeners('foo'));
304        $this->assertFalse($this->dispatcher->hasListeners());
305    }
306
307    public function testHasListenersIsLazy()
308    {
309        $called = 0;
310        $listener = [function () use (&$called) { ++$called; }, 'onFoo'];
311        $this->dispatcher->addListener('foo', $listener);
312        $this->assertTrue($this->dispatcher->hasListeners());
313        $this->assertTrue($this->dispatcher->hasListeners('foo'));
314        $this->assertSame(0, $called);
315    }
316
317    public function testDispatchLazyListener()
318    {
319        $called = 0;
320        $factory = function () use (&$called) {
321            ++$called;
322
323            return new TestWithDispatcher();
324        };
325        $this->dispatcher->addListener('foo', [$factory, 'foo']);
326        $this->assertSame(0, $called);
327        $this->dispatcher->dispatch('foo', new Event());
328        $this->dispatcher->dispatch('foo', new Event());
329        $this->assertSame(1, $called);
330    }
331
332    public function testRemoveFindsLazyListeners()
333    {
334        $test = new TestWithDispatcher();
335        $factory = function () use ($test) { return $test; };
336
337        $this->dispatcher->addListener('foo', [$factory, 'foo']);
338        $this->assertTrue($this->dispatcher->hasListeners('foo'));
339        $this->dispatcher->removeListener('foo', [$test, 'foo']);
340        $this->assertFalse($this->dispatcher->hasListeners('foo'));
341
342        $this->dispatcher->addListener('foo', [$test, 'foo']);
343        $this->assertTrue($this->dispatcher->hasListeners('foo'));
344        $this->dispatcher->removeListener('foo', [$factory, 'foo']);
345        $this->assertFalse($this->dispatcher->hasListeners('foo'));
346    }
347
348    public function testPriorityFindsLazyListeners()
349    {
350        $test = new TestWithDispatcher();
351        $factory = function () use ($test) { return $test; };
352
353        $this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
354        $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', [$test, 'foo']));
355        $this->dispatcher->removeListener('foo', [$factory, 'foo']);
356
357        $this->dispatcher->addListener('foo', [$test, 'foo'], 5);
358        $this->assertSame(5, $this->dispatcher->getListenerPriority('foo', [$factory, 'foo']));
359    }
360
361    public function testGetLazyListeners()
362    {
363        $test = new TestWithDispatcher();
364        $factory = function () use ($test) { return $test; };
365
366        $this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
367        $this->assertSame([[$test, 'foo']], $this->dispatcher->getListeners('foo'));
368
369        $this->dispatcher->removeListener('foo', [$test, 'foo']);
370        $this->dispatcher->addListener('bar', [$factory, 'foo'], 3);
371        $this->assertSame(['bar' => [[$test, 'foo']]], $this->dispatcher->getListeners());
372    }
373}
374
375class CallableClass
376{
377    public function __invoke()
378    {
379    }
380}
381
382class TestEventListener
383{
384    public $preFooInvoked = false;
385    public $postFooInvoked = false;
386
387    /* Listener methods */
388
389    public function preFoo(Event $e)
390    {
391        $this->preFooInvoked = true;
392    }
393
394    public function postFoo(Event $e)
395    {
396        $this->postFooInvoked = true;
397
398        $e->stopPropagation();
399    }
400}
401
402class TestWithDispatcher
403{
404    public $name;
405    public $dispatcher;
406
407    public function foo(Event $e, $name, $dispatcher)
408    {
409        $this->name = $name;
410        $this->dispatcher = $dispatcher;
411    }
412}
413
414class TestEventSubscriber implements EventSubscriberInterface
415{
416    public static function getSubscribedEvents()
417    {
418        return ['pre.foo' => 'preFoo', 'post.foo' => 'postFoo'];
419    }
420}
421
422class TestEventSubscriberWithPriorities implements EventSubscriberInterface
423{
424    public static function getSubscribedEvents()
425    {
426        return [
427            'pre.foo' => ['preFoo', 10],
428            'post.foo' => ['postFoo'],
429        ];
430    }
431}
432
433class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
434{
435    public static function getSubscribedEvents()
436    {
437        return ['pre.foo' => [
438            ['preFoo1'],
439            ['preFoo2', 10],
440        ]];
441    }
442}
443