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