* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\EventDispatcher\Tests; use PHPUnit\Framework\TestCase; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; class EventDispatcherTest extends TestCase { /* Some pseudo events */ private const preFoo = 'pre.foo'; private const postFoo = 'post.foo'; private const preBar = 'pre.bar'; /** * @var EventDispatcher */ private $dispatcher; private $listener; protected function setUp(): void { $this->dispatcher = $this->createEventDispatcher(); $this->listener = new TestEventListener(); } protected function tearDown(): void { $this->dispatcher = null; $this->listener = null; } protected function createEventDispatcher() { return new EventDispatcher(); } public function testInitialState() { $this->assertEquals([], $this->dispatcher->getListeners()); $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); } public function testAddListener() { $this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']); $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']); $this->assertTrue($this->dispatcher->hasListeners()); $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo)); $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo)); $this->assertCount(2, $this->dispatcher->getListeners()); } public function testGetListenersSortsByPriority() { $listener1 = new TestEventListener(); $listener2 = new TestEventListener(); $listener3 = new TestEventListener(); $listener1->name = '1'; $listener2->name = '2'; $listener3->name = '3'; $this->dispatcher->addListener('pre.foo', [$listener1, 'preFoo'], -10); $this->dispatcher->addListener('pre.foo', [$listener2, 'preFoo'], 10); $this->dispatcher->addListener('pre.foo', [$listener3, 'preFoo']); $expected = [ [$listener2, 'preFoo'], [$listener3, 'preFoo'], [$listener1, 'preFoo'], ]; $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo')); } public function testGetAllListenersSortsByPriority() { $listener1 = new TestEventListener(); $listener2 = new TestEventListener(); $listener3 = new TestEventListener(); $listener4 = new TestEventListener(); $listener5 = new TestEventListener(); $listener6 = new TestEventListener(); $this->dispatcher->addListener('pre.foo', $listener1, -10); $this->dispatcher->addListener('pre.foo', $listener2); $this->dispatcher->addListener('pre.foo', $listener3, 10); $this->dispatcher->addListener('post.foo', $listener4, -10); $this->dispatcher->addListener('post.foo', $listener5); $this->dispatcher->addListener('post.foo', $listener6, 10); $expected = [ 'pre.foo' => [$listener3, $listener2, $listener1], 'post.foo' => [$listener6, $listener5, $listener4], ]; $this->assertSame($expected, $this->dispatcher->getListeners()); } public function testGetListenerPriority() { $listener1 = new TestEventListener(); $listener2 = new TestEventListener(); $this->dispatcher->addListener('pre.foo', $listener1, -10); $this->dispatcher->addListener('pre.foo', $listener2); $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1)); $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2)); $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2)); $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {})); } public function testDispatch() { $this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']); $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']); $this->dispatcher->dispatch(new Event(), self::preFoo); $this->assertTrue($this->listener->preFooInvoked); $this->assertFalse($this->listener->postFooInvoked); $this->assertInstanceOf(Event::class, $this->dispatcher->dispatch(new Event(), 'noevent')); $this->assertInstanceOf(Event::class, $this->dispatcher->dispatch(new Event(), self::preFoo)); $event = new Event(); $return = $this->dispatcher->dispatch($event, self::preFoo); $this->assertSame($event, $return); } public function testDispatchContractsEvent() { $this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']); $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']); $this->dispatcher->dispatch(new ContractsEvent(), self::preFoo); $this->assertTrue($this->listener->preFooInvoked); $this->assertFalse($this->listener->postFooInvoked); $this->assertInstanceOf(Event::class, $this->dispatcher->dispatch(new Event(), 'noevent')); $this->assertInstanceOf(Event::class, $this->dispatcher->dispatch(new Event(), self::preFoo)); $event = new Event(); $return = $this->dispatcher->dispatch($event, self::preFoo); $this->assertSame($event, $return); } public function testDispatchForClosure() { $invoked = 0; $listener = function () use (&$invoked) { ++$invoked; }; $this->dispatcher->addListener('pre.foo', $listener); $this->dispatcher->addListener('post.foo', $listener); $this->dispatcher->dispatch(new Event(), self::preFoo); $this->assertEquals(1, $invoked); } public function testStopEventPropagation() { $otherListener = new TestEventListener(); // postFoo() stops the propagation, so only one listener should // be executed // Manually set priority to enforce $this->listener to be called first $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo'], 10); $this->dispatcher->addListener('post.foo', [$otherListener, 'postFoo']); $this->dispatcher->dispatch(new Event(), self::postFoo); $this->assertTrue($this->listener->postFooInvoked); $this->assertFalse($otherListener->postFooInvoked); } public function testDispatchByPriority() { $invoked = []; $listener1 = function () use (&$invoked) { $invoked[] = '1'; }; $listener2 = function () use (&$invoked) { $invoked[] = '2'; }; $listener3 = function () use (&$invoked) { $invoked[] = '3'; }; $this->dispatcher->addListener('pre.foo', $listener1, -10); $this->dispatcher->addListener('pre.foo', $listener2); $this->dispatcher->addListener('pre.foo', $listener3, 10); $this->dispatcher->dispatch(new Event(), self::preFoo); $this->assertEquals(['3', '2', '1'], $invoked); } public function testRemoveListener() { $this->dispatcher->addListener('pre.bar', $this->listener); $this->assertTrue($this->dispatcher->hasListeners(self::preBar)); $this->dispatcher->removeListener('pre.bar', $this->listener); $this->assertFalse($this->dispatcher->hasListeners(self::preBar)); $this->dispatcher->removeListener('notExists', $this->listener); } public function testAddSubscriber() { $eventSubscriber = new TestEventSubscriber(); $this->dispatcher->addSubscriber($eventSubscriber); $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); } public function testAddSubscriberWithPriorities() { $eventSubscriber = new TestEventSubscriber(); $this->dispatcher->addSubscriber($eventSubscriber); $eventSubscriber = new TestEventSubscriberWithPriorities(); $this->dispatcher->addSubscriber($eventSubscriber); $listeners = $this->dispatcher->getListeners('pre.foo'); $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); $this->assertCount(2, $listeners); $this->assertInstanceOf(TestEventSubscriberWithPriorities::class, $listeners[0][0]); } public function testAddSubscriberWithMultipleListeners() { $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); $this->dispatcher->addSubscriber($eventSubscriber); $listeners = $this->dispatcher->getListeners('pre.foo'); $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); $this->assertCount(2, $listeners); $this->assertEquals('preFoo2', $listeners[0][1]); } public function testRemoveSubscriber() { $eventSubscriber = new TestEventSubscriber(); $this->dispatcher->addSubscriber($eventSubscriber); $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); $this->dispatcher->removeSubscriber($eventSubscriber); $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); } public function testRemoveSubscriberWithPriorities() { $eventSubscriber = new TestEventSubscriberWithPriorities(); $this->dispatcher->addSubscriber($eventSubscriber); $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); $this->dispatcher->removeSubscriber($eventSubscriber); $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); } public function testRemoveSubscriberWithMultipleListeners() { $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); $this->dispatcher->addSubscriber($eventSubscriber); $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo)); $this->dispatcher->removeSubscriber($eventSubscriber); $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); } public function testEventReceivesTheDispatcherInstanceAsArgument() { $listener = new TestWithDispatcher(); $this->dispatcher->addListener('test', [$listener, 'foo']); $this->assertNull($listener->name); $this->assertNull($listener->dispatcher); $this->dispatcher->dispatch(new Event(), 'test'); $this->assertEquals('test', $listener->name); $this->assertSame($this->dispatcher, $listener->dispatcher); } /** * @see https://bugs.php.net/62976 * * This bug affects: * - The PHP 5.3 branch for versions < 5.3.18 * - The PHP 5.4 branch for versions < 5.4.8 * - The PHP 5.5 branch is not affected */ public function testWorkaroundForPhpBug62976() { $dispatcher = $this->createEventDispatcher(); $dispatcher->addListener('bug.62976', new CallableClass()); $dispatcher->removeListener('bug.62976', function () {}); $this->assertTrue($dispatcher->hasListeners('bug.62976')); } public function testHasListenersWhenAddedCallbackListenerIsRemoved() { $listener = function () {}; $this->dispatcher->addListener('foo', $listener); $this->dispatcher->removeListener('foo', $listener); $this->assertFalse($this->dispatcher->hasListeners()); } public function testGetListenersWhenAddedCallbackListenerIsRemoved() { $listener = function () {}; $this->dispatcher->addListener('foo', $listener); $this->dispatcher->removeListener('foo', $listener); $this->assertSame([], $this->dispatcher->getListeners()); } public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() { $this->assertFalse($this->dispatcher->hasListeners('foo')); $this->assertFalse($this->dispatcher->hasListeners()); } public function testHasListenersIsLazy() { $called = 0; $listener = [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() { $dispatcher = new TestWithDispatcher(); $called = 0; $factory = function () use (&$called, $dispatcher) { ++$called; return $dispatcher; }; $this->dispatcher->addListener('foo', [$factory, 'foo']); $this->assertSame(0, $called); $this->dispatcher->dispatch(new Event(), 'foo'); $this->assertFalse($dispatcher->invoked); $this->dispatcher->dispatch(new Event(), 'foo'); $this->assertSame(1, $called); $this->dispatcher->addListener('bar', [$factory]); $this->assertSame(1, $called); $this->dispatcher->dispatch(new Event(), 'bar'); $this->assertTrue($dispatcher->invoked); $this->dispatcher->dispatch(new Event(), 'bar'); $this->assertSame(2, $called); } public function testRemoveFindsLazyListeners() { $test = new TestWithDispatcher(); $factory = function () use ($test) { return $test; }; $this->dispatcher->addListener('foo', [$factory, 'foo']); $this->assertTrue($this->dispatcher->hasListeners('foo')); $this->dispatcher->removeListener('foo', [$test, 'foo']); $this->assertFalse($this->dispatcher->hasListeners('foo')); $this->dispatcher->addListener('foo', [$test, 'foo']); $this->assertTrue($this->dispatcher->hasListeners('foo')); $this->dispatcher->removeListener('foo', [$factory, 'foo']); $this->assertFalse($this->dispatcher->hasListeners('foo')); } public function testPriorityFindsLazyListeners() { $test = new TestWithDispatcher(); $factory = function () use ($test) { return $test; }; $this->dispatcher->addListener('foo', [$factory, 'foo'], 3); $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', [$test, 'foo'])); $this->dispatcher->removeListener('foo', [$factory, 'foo']); $this->dispatcher->addListener('foo', [$test, 'foo'], 5); $this->assertSame(5, $this->dispatcher->getListenerPriority('foo', [$factory, 'foo'])); } public function testGetLazyListeners() { $test = new TestWithDispatcher(); $factory = function () use ($test) { return $test; }; $this->dispatcher->addListener('foo', [$factory, 'foo'], 3); $this->assertSame([[$test, 'foo']], $this->dispatcher->getListeners('foo')); $this->dispatcher->removeListener('foo', [$test, 'foo']); $this->dispatcher->addListener('bar', [$factory, 'foo'], 3); $this->assertSame(['bar' => [[$test, 'foo']]], $this->dispatcher->getListeners()); } public function testMutatingWhilePropagationIsStopped() { $testLoaded = false; $test = new TestEventListener(); $this->dispatcher->addListener('foo', [$test, 'postFoo']); $this->dispatcher->addListener('foo', [function () use ($test, &$testLoaded) { $testLoaded = true; return $test; }, 'preFoo']); $this->dispatcher->dispatch(new Event(), 'foo'); $this->assertTrue($test->postFooInvoked); $this->assertFalse($test->preFooInvoked); $this->assertsame(0, $this->dispatcher->getListenerPriority('foo', [$test, 'preFoo'])); $test->preFoo(new Event()); $this->dispatcher->dispatch(new Event(), 'foo'); $this->assertTrue($testLoaded); } /** * @requires PHP 8.1 */ public function testNamedClosures() { $listener = new TestEventListener(); $callback1 = \Closure::fromCallable($listener); $callback2 = \Closure::fromCallable($listener); $callback3 = \Closure::fromCallable(new TestEventListener()); $this->assertNotSame($callback1, $callback2); $this->assertNotSame($callback1, $callback3); $this->assertNotSame($callback2, $callback3); $this->assertTrue($callback1 == $callback2); $this->assertFalse($callback1 == $callback3); $this->dispatcher->addListener('foo', $callback1, 3); $this->dispatcher->addListener('foo', $callback2, 2); $this->dispatcher->addListener('foo', $callback3, 1); $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', $callback1)); $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', $callback2)); $this->dispatcher->removeListener('foo', $callback1); $this->assertSame(['foo' => [$callback3]], $this->dispatcher->getListeners()); } /** * @group legacy * @expectedDeprecation Calling the "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead. */ public function testLegacySignatureWithoutEvent() { $this->dispatcher->dispatch('foo'); } /** * @group legacy * @expectedDeprecation Calling the "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead. */ public function testLegacySignatureWithEvent() { $this->dispatcher->dispatch('foo', new Event()); } /** * @group legacy * @expectedDeprecation Calling the "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead. */ public function testLegacySignatureWithNewEventObject() { $this->dispatcher->dispatch('foo', new ContractsEvent()); } } class CallableClass { public function __invoke() { } } class TestEventListener { public $name; public $preFooInvoked = false; public $postFooInvoked = false; /* Listener methods */ public function preFoo($e) { $this->preFooInvoked = true; } public function postFoo($e) { $this->postFooInvoked = true; if (!$this->preFooInvoked) { $e->stopPropagation(); } } public function __invoke() { } } class TestWithDispatcher { public $name; public $dispatcher; public $invoked = false; public function foo($e, $name, $dispatcher) { $this->name = $name; $this->dispatcher = $dispatcher; } public function __invoke($e, $name, $dispatcher) { $this->name = $name; $this->dispatcher = $dispatcher; $this->invoked = true; } } class TestEventSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return ['pre.foo' => 'preFoo', 'post.foo' => 'postFoo']; } } class TestEventSubscriberWithPriorities implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ 'pre.foo' => ['preFoo', 10], 'post.foo' => ['postFoo'], ]; } } class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return ['pre.foo' => [ ['preFoo1'], ['preFoo2', 10], ]]; } }