ContainerAwareEventDispatcher.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <?php
  2. namespace Drupal\Component\EventDispatcher;
  3. use Symfony\Component\DependencyInjection\ContainerInterface;
  4. use Symfony\Component\EventDispatcher\Event;
  5. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  6. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  7. /**
  8. * A performance optimized container aware event dispatcher.
  9. *
  10. * This version of the event dispatcher contains the following optimizations
  11. * in comparison to the Symfony event dispatcher component:
  12. *
  13. * <dl>
  14. * <dt>Faster instantiation of the event dispatcher service</dt>
  15. * <dd>
  16. * Instead of calling <code>addSubscriberService</code> once for each
  17. * subscriber, a precompiled array of listener definitions is passed
  18. * directly to the constructor. This is faster by roughly an order of
  19. * magnitude. The listeners are collected and prepared using a compiler
  20. * pass.
  21. * </dd>
  22. * <dt>Lazy instantiation of listeners</dt>
  23. * <dd>
  24. * Services are only retrieved from the container just before invocation.
  25. * Especially when dispatching the KernelEvents::REQUEST event, this leads
  26. * to a more timely invocation of the first listener. Overall dispatch
  27. * runtime is not affected by this change though.
  28. * </dd>
  29. * </dl>
  30. */
  31. class ContainerAwareEventDispatcher implements EventDispatcherInterface {
  32. /**
  33. * The service container.
  34. *
  35. * @var \Symfony\Component\DependencyInjection\ContainerInterface
  36. */
  37. protected $container;
  38. /**
  39. * Listener definitions.
  40. *
  41. * A nested array of listener definitions keyed by event name and priority.
  42. * A listener definition is an associative array with one of the following key
  43. * value pairs:
  44. * - callable: A callable listener
  45. * - service: An array of the form [service id, method]
  46. *
  47. * A service entry will be resolved to a callable only just before its
  48. * invocation.
  49. *
  50. * @var array
  51. */
  52. protected $listeners;
  53. /**
  54. * Whether listeners need to be sorted prior to dispatch, keyed by event name.
  55. *
  56. * @var TRUE[]
  57. */
  58. protected $unsorted;
  59. /**
  60. * Constructs a container aware event dispatcher.
  61. *
  62. * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
  63. * The service container.
  64. * @param array $listeners
  65. * A nested array of listener definitions keyed by event name and priority.
  66. * The array is expected to be ordered by priority. A listener definition is
  67. * an associative array with one of the following key value pairs:
  68. * - callable: A callable listener
  69. * - service: An array of the form [service id, method]
  70. * A service entry will be resolved to a callable only just before its
  71. * invocation.
  72. */
  73. public function __construct(ContainerInterface $container, array $listeners = []) {
  74. $this->container = $container;
  75. $this->listeners = $listeners;
  76. $this->unsorted = [];
  77. }
  78. /**
  79. * {@inheritdoc}
  80. */
  81. public function dispatch($event_name, Event $event = NULL) {
  82. if ($event === NULL) {
  83. $event = new Event();
  84. }
  85. if (isset($this->listeners[$event_name])) {
  86. // Sort listeners if necessary.
  87. if (isset($this->unsorted[$event_name])) {
  88. krsort($this->listeners[$event_name]);
  89. unset($this->unsorted[$event_name]);
  90. }
  91. // Invoke listeners and resolve callables if necessary.
  92. foreach ($this->listeners[$event_name] as $priority => &$definitions) {
  93. foreach ($definitions as $key => &$definition) {
  94. if (!isset($definition['callable'])) {
  95. $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
  96. }
  97. if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
  98. $definition['callable'][0] = $definition['callable'][0]();
  99. }
  100. call_user_func($definition['callable'], $event, $event_name, $this);
  101. if ($event->isPropagationStopped()) {
  102. return $event;
  103. }
  104. }
  105. }
  106. }
  107. return $event;
  108. }
  109. /**
  110. * {@inheritdoc}
  111. */
  112. public function getListeners($event_name = NULL) {
  113. $result = [];
  114. if ($event_name === NULL) {
  115. // If event name was omitted, collect all listeners of all events.
  116. foreach (array_keys($this->listeners) as $event_name) {
  117. $listeners = $this->getListeners($event_name);
  118. if (!empty($listeners)) {
  119. $result[$event_name] = $listeners;
  120. }
  121. }
  122. }
  123. elseif (isset($this->listeners[$event_name])) {
  124. // Sort listeners if necessary.
  125. if (isset($this->unsorted[$event_name])) {
  126. krsort($this->listeners[$event_name]);
  127. unset($this->unsorted[$event_name]);
  128. }
  129. // Collect listeners and resolve callables if necessary.
  130. foreach ($this->listeners[$event_name] as $priority => &$definitions) {
  131. foreach ($definitions as $key => &$definition) {
  132. if (!isset($definition['callable'])) {
  133. $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
  134. }
  135. if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
  136. $definition['callable'][0] = $definition['callable'][0]();
  137. }
  138. $result[] = $definition['callable'];
  139. }
  140. }
  141. }
  142. return $result;
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function getListenerPriority($event_name, $listener) {
  148. if (!isset($this->listeners[$event_name])) {
  149. return;
  150. }
  151. if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
  152. $listener[0] = $listener[0]();
  153. }
  154. // Resolve service definitions if the listener has not been found so far.
  155. foreach ($this->listeners[$event_name] as $priority => &$definitions) {
  156. foreach ($definitions as $key => &$definition) {
  157. if (!isset($definition['callable'])) {
  158. // Once the callable is retrieved we keep it for subsequent method
  159. // invocations on this class.
  160. $definition['callable'] = [
  161. $this->container->get($definition['service'][0]),
  162. $definition['service'][1],
  163. ];
  164. }
  165. if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
  166. $definition['callable'][0] = $definition['callable'][0]();
  167. }
  168. if ($definition['callable'] === $listener) {
  169. return $priority;
  170. }
  171. }
  172. }
  173. }
  174. /**
  175. * {@inheritdoc}
  176. */
  177. public function hasListeners($event_name = NULL) {
  178. if ($event_name !== NULL) {
  179. return !empty($this->listeners[$event_name]);
  180. }
  181. foreach ($this->listeners as $event_listeners) {
  182. if ($event_listeners) {
  183. return TRUE;
  184. }
  185. }
  186. return FALSE;
  187. }
  188. /**
  189. * {@inheritdoc}
  190. */
  191. public function addListener($event_name, $listener, $priority = 0) {
  192. $this->listeners[$event_name][$priority][] = ['callable' => $listener];
  193. $this->unsorted[$event_name] = TRUE;
  194. }
  195. /**
  196. * {@inheritdoc}
  197. */
  198. public function removeListener($event_name, $listener) {
  199. if (!isset($this->listeners[$event_name])) {
  200. return;
  201. }
  202. foreach ($this->listeners[$event_name] as $priority => $definitions) {
  203. foreach ($definitions as $key => $definition) {
  204. if (!isset($definition['callable'])) {
  205. if (!$this->container->initialized($definition['service'][0])) {
  206. continue;
  207. }
  208. $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
  209. }
  210. if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure && !$listener instanceof \Closure) {
  211. $definition['callable'][0] = $definition['callable'][0]();
  212. }
  213. if (is_array($definition['callable']) && isset($definition['callable'][0]) && !$definition['callable'][0] instanceof \Closure && is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
  214. $listener[0] = $listener[0]();
  215. }
  216. if ($definition['callable'] === $listener) {
  217. unset($definitions[$key]);
  218. }
  219. }
  220. if ($definitions) {
  221. $this->listeners[$event_name][$priority] = $definitions;
  222. }
  223. else {
  224. unset($this->listeners[$event_name][$priority]);
  225. }
  226. }
  227. }
  228. /**
  229. * {@inheritdoc}
  230. */
  231. public function addSubscriber(EventSubscriberInterface $subscriber) {
  232. foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
  233. if (is_string($params)) {
  234. $this->addListener($event_name, [$subscriber, $params]);
  235. }
  236. elseif (is_string($params[0])) {
  237. $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
  238. }
  239. else {
  240. foreach ($params as $listener) {
  241. $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
  242. }
  243. }
  244. }
  245. }
  246. /**
  247. * {@inheritdoc}
  248. */
  249. public function removeSubscriber(EventSubscriberInterface $subscriber) {
  250. foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
  251. if (is_array($params) && is_array($params[0])) {
  252. foreach ($params as $listener) {
  253. $this->removeListener($event_name, [$subscriber, $listener[0]]);
  254. }
  255. }
  256. else {
  257. $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);
  258. }
  259. }
  260. }
  261. }