EventDispatcherTest.php 19 KB

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