EventDispatcher.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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;
  11. use Psr\EventDispatcher\StoppableEventInterface;
  12. use Symfony\Component\EventDispatcher\Debug\WrappedListener;
  13. use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
  14. /**
  15. * The EventDispatcherInterface is the central point of Symfony's event listener system.
  16. *
  17. * Listeners are registered on the manager and events are dispatched through the
  18. * manager.
  19. *
  20. * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
  21. * @author Jonathan Wage <jonwage@gmail.com>
  22. * @author Roman Borschel <roman@code-factory.org>
  23. * @author Bernhard Schussek <bschussek@gmail.com>
  24. * @author Fabien Potencier <fabien@symfony.com>
  25. * @author Jordi Boggiano <j.boggiano@seld.be>
  26. * @author Jordan Alliot <jordan.alliot@gmail.com>
  27. * @author Nicolas Grekas <p@tchwork.com>
  28. */
  29. class EventDispatcher implements EventDispatcherInterface
  30. {
  31. private $listeners = [];
  32. private $sorted = [];
  33. private $optimized;
  34. public function __construct()
  35. {
  36. if (__CLASS__ === static::class) {
  37. $this->optimized = [];
  38. }
  39. }
  40. /**
  41. * {@inheritdoc}
  42. *
  43. * @param string|null $eventName
  44. */
  45. public function dispatch($event/*, string $eventName = null*/)
  46. {
  47. $eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
  48. if (\is_object($event)) {
  49. $eventName = $eventName ?? \get_class($event);
  50. } elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) {
  51. @trigger_error(sprintf('Calling the "%s::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.', EventDispatcherInterface::class), \E_USER_DEPRECATED);
  52. $swap = $event;
  53. $event = $eventName ?? new Event();
  54. $eventName = $swap;
  55. } else {
  56. throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, "%s" given.', EventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event)));
  57. }
  58. if (null !== $this->optimized && null !== $eventName) {
  59. $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
  60. } else {
  61. $listeners = $this->getListeners($eventName);
  62. }
  63. if ($listeners) {
  64. $this->callListeners($listeners, $eventName, $event);
  65. }
  66. return $event;
  67. }
  68. /**
  69. * {@inheritdoc}
  70. */
  71. public function getListeners($eventName = null)
  72. {
  73. if (null !== $eventName) {
  74. if (empty($this->listeners[$eventName])) {
  75. return [];
  76. }
  77. if (!isset($this->sorted[$eventName])) {
  78. $this->sortListeners($eventName);
  79. }
  80. return $this->sorted[$eventName];
  81. }
  82. foreach ($this->listeners as $eventName => $eventListeners) {
  83. if (!isset($this->sorted[$eventName])) {
  84. $this->sortListeners($eventName);
  85. }
  86. }
  87. return array_filter($this->sorted);
  88. }
  89. /**
  90. * {@inheritdoc}
  91. */
  92. public function getListenerPriority($eventName, $listener)
  93. {
  94. if (empty($this->listeners[$eventName])) {
  95. return null;
  96. }
  97. if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
  98. $listener[0] = $listener[0]();
  99. $listener[1] = $listener[1] ?? '__invoke';
  100. }
  101. foreach ($this->listeners[$eventName] as $priority => &$listeners) {
  102. foreach ($listeners as &$v) {
  103. if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
  104. $v[0] = $v[0]();
  105. $v[1] = $v[1] ?? '__invoke';
  106. }
  107. if ($v === $listener) {
  108. return $priority;
  109. }
  110. }
  111. }
  112. return null;
  113. }
  114. /**
  115. * {@inheritdoc}
  116. */
  117. public function hasListeners($eventName = null)
  118. {
  119. if (null !== $eventName) {
  120. return !empty($this->listeners[$eventName]);
  121. }
  122. foreach ($this->listeners as $eventListeners) {
  123. if ($eventListeners) {
  124. return true;
  125. }
  126. }
  127. return false;
  128. }
  129. /**
  130. * {@inheritdoc}
  131. */
  132. public function addListener($eventName, $listener, $priority = 0)
  133. {
  134. $this->listeners[$eventName][$priority][] = $listener;
  135. unset($this->sorted[$eventName], $this->optimized[$eventName]);
  136. }
  137. /**
  138. * {@inheritdoc}
  139. */
  140. public function removeListener($eventName, $listener)
  141. {
  142. if (empty($this->listeners[$eventName])) {
  143. return;
  144. }
  145. if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
  146. $listener[0] = $listener[0]();
  147. $listener[1] = $listener[1] ?? '__invoke';
  148. }
  149. foreach ($this->listeners[$eventName] as $priority => &$listeners) {
  150. foreach ($listeners as $k => &$v) {
  151. if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
  152. $v[0] = $v[0]();
  153. $v[1] = $v[1] ?? '__invoke';
  154. }
  155. if ($v === $listener) {
  156. unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
  157. }
  158. }
  159. if (!$listeners) {
  160. unset($this->listeners[$eventName][$priority]);
  161. }
  162. }
  163. }
  164. /**
  165. * {@inheritdoc}
  166. */
  167. public function addSubscriber(EventSubscriberInterface $subscriber)
  168. {
  169. foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
  170. if (\is_string($params)) {
  171. $this->addListener($eventName, [$subscriber, $params]);
  172. } elseif (\is_string($params[0])) {
  173. $this->addListener($eventName, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
  174. } else {
  175. foreach ($params as $listener) {
  176. $this->addListener($eventName, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
  177. }
  178. }
  179. }
  180. }
  181. /**
  182. * {@inheritdoc}
  183. */
  184. public function removeSubscriber(EventSubscriberInterface $subscriber)
  185. {
  186. foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
  187. if (\is_array($params) && \is_array($params[0])) {
  188. foreach ($params as $listener) {
  189. $this->removeListener($eventName, [$subscriber, $listener[0]]);
  190. }
  191. } else {
  192. $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
  193. }
  194. }
  195. }
  196. /**
  197. * Triggers the listeners of an event.
  198. *
  199. * This method can be overridden to add functionality that is executed
  200. * for each listener.
  201. *
  202. * @param callable[] $listeners The event listeners
  203. * @param string $eventName The name of the event to dispatch
  204. * @param object $event The event object to pass to the event handlers/listeners
  205. */
  206. protected function callListeners(iterable $listeners, string $eventName, $event)
  207. {
  208. if ($event instanceof Event) {
  209. $this->doDispatch($listeners, $eventName, $event);
  210. return;
  211. }
  212. $stoppable = $event instanceof ContractsEvent || $event instanceof StoppableEventInterface;
  213. foreach ($listeners as $listener) {
  214. if ($stoppable && $event->isPropagationStopped()) {
  215. break;
  216. }
  217. // @deprecated: the ternary operator is part of a BC layer and should be removed in 5.0
  218. $listener($listener instanceof WrappedListener ? new LegacyEventProxy($event) : $event, $eventName, $this);
  219. }
  220. }
  221. /**
  222. * @deprecated since Symfony 4.3, use callListeners() instead
  223. */
  224. protected function doDispatch($listeners, $eventName, Event $event)
  225. {
  226. foreach ($listeners as $listener) {
  227. if ($event->isPropagationStopped()) {
  228. break;
  229. }
  230. $listener($event, $eventName, $this);
  231. }
  232. }
  233. /**
  234. * Sorts the internal list of listeners for the given event by priority.
  235. */
  236. private function sortListeners(string $eventName)
  237. {
  238. krsort($this->listeners[$eventName]);
  239. $this->sorted[$eventName] = [];
  240. foreach ($this->listeners[$eventName] as &$listeners) {
  241. foreach ($listeners as $k => &$listener) {
  242. if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
  243. $listener[0] = $listener[0]();
  244. $listener[1] = $listener[1] ?? '__invoke';
  245. }
  246. $this->sorted[$eventName][] = $listener;
  247. }
  248. }
  249. }
  250. /**
  251. * Optimizes the internal list of listeners for the given event by priority.
  252. */
  253. private function optimizeListeners(string $eventName): array
  254. {
  255. krsort($this->listeners[$eventName]);
  256. $this->optimized[$eventName] = [];
  257. foreach ($this->listeners[$eventName] as &$listeners) {
  258. foreach ($listeners as &$listener) {
  259. $closure = &$this->optimized[$eventName][];
  260. if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
  261. $closure = static function (...$args) use (&$listener, &$closure) {
  262. if ($listener[0] instanceof \Closure) {
  263. $listener[0] = $listener[0]();
  264. $listener[1] = $listener[1] ?? '__invoke';
  265. }
  266. ($closure = \Closure::fromCallable($listener))(...$args);
  267. };
  268. } else {
  269. $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener);
  270. }
  271. }
  272. }
  273. return $this->optimized[$eventName];
  274. }
  275. }