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