diff --git a/core/core.services.yml b/core/core.services.yml index f5ed5db..87abd1a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -380,7 +380,7 @@ services: class: Drupal\Core\Routing\CurrentRouteMatch arguments: ['@request_stack'] event_dispatcher: - class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher + class: Drupal\Core\EventSubscriber\ContainerAwareEventDispatcher arguments: ['@service_container'] controller_resolver: class: Drupal\Core\Controller\ControllerResolver diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php index 59a3591..1c607cb 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php @@ -18,6 +18,8 @@ public function process(ContainerBuilder $container) { $definition = $container->getDefinition('event_dispatcher'); + $event_subscriber_info = []; + $weights = []; foreach ($container->findTaggedServiceIds('event_subscriber') as $id => $attributes) { // We must assume that the class value has been correctly filled, even if the service is created by a factory @@ -28,7 +30,28 @@ public function process(ContainerBuilder $container) { if (!$refClass->implementsInterface($interface)) { throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); } - $definition->addMethodCall('addSubscriberService', array($id, $class)); + + // Get all subscribed events. + foreach ($class::getSubscribedEvents() as $event_name => $params) { + if (is_string($params)) { + $event_subscriber_info[$event_name][] = array($id, $params); + $weights[$event_name][] = 0; + } + elseif (is_string($params[0])) { + $event_subscriber_info[$event_name][] = array($id, $params[0]); + $weights[$event_name][] = isset($params[1]) ? $params[1] : 0; + } + else { + foreach ($params as $listener) { + $event_subscriber_info[$event_name][] = array($id, $listener[0]); + $weights[$event_name][] = isset($listener[1]) ? $listener[1] : 0; + } + } + } } + foreach ($weights as $event_name => $sort_weights) { + array_multisort($sort_weights, SORT_NUMERIC, $event_subscriber_info[$event_name]); + } + $definition->addArgument($event_subscriber_info); } } diff --git a/core/lib/Drupal/Core/EventSubscriber/ContainerAwareEventDispatcher.php b/core/lib/Drupal/Core/EventSubscriber/ContainerAwareEventDispatcher.php new file mode 100644 index 0000000..ca466dd --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/ContainerAwareEventDispatcher.php @@ -0,0 +1,214 @@ +container = $container; + $this->listenerIds = $listener_ids; + } + + /** + * {@inheritDoc} + * + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @throws \InvalidArgumentException if the service is not defined + */ + public function dispatch($eventName, Event $event = NULL) { + if (null === $event) { + $event = new Event(); + } + + $event->setDispatcher($this); + $event->setName($eventName); + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $args) { + list($serviceId, $method) = $args; + $listener = $this->container->get($serviceId); + $listener::$method($event, $eventName, $this); + if ($event->isPropagationStopped()) { + break; + } + } + } + return $event; + } + + /** + * Adds a service as event listener + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param integer $priority The higher this value, the earlier an event + * listener will be triggered in the chain. Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) { + if (!is_array($callback) || 2 !== count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array( + $callback[0], + $callback[1], + $priority + ); + } + + public function removeListener($eventName, $listener) { + $this->lazyLoad($eventName); + + if (isset($this->listeners[$eventName])) { + foreach ($this->listeners[$eventName] as $key => $l) { + foreach ($this->listenerIds[$eventName] as $i => $args) { + list($serviceId, $method, $priority) = $args; + if ($key === $serviceId . '.' . $method) { + if ($listener === array($l, $method)) { + unset($this->listeners[$eventName][$key]); + if (empty($this->listeners[$eventName])) { + unset($this->listeners[$eventName]); + } + unset($this->listenerIds[$eventName][$i]); + if (empty($this->listenerIds[$eventName])) { + unset($this->listenerIds[$eventName]); + } + } + } + } + } + } + + parent::removeListener($eventName, $listener); + } + + /** + * @see EventDispatcherInterface::hasListeners + */ + public function hasListeners($eventName = NULL) { + if (NULL === $eventName) { + return (Boolean) count($this->listenerIds) || (Boolean) count($this->listeners); + } + + if (isset($this->listenerIds[$eventName])) { + return TRUE; + } + + return parent::hasListeners($eventName); + } + + /** + * @see EventDispatcherInterface::getListeners + */ + public function getListeners($eventName = NULL) { + if (NULL === $eventName) { + foreach (array_keys($this->listenerIds) as $serviceEventName) { + $this->lazyLoad($serviceEventName); + } + } + else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * Adds a service as event subscriber + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement + * EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) { + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } + elseif (is_string($params[0])) { + $this->listenerIds[$eventName][] = array( + $serviceId, + $params[0], + isset($params[1]) ? $params[1] : 0 + ); + } + else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array( + $serviceId, + $listener[0], + isset($listener[1]) ? $listener[1] : 0 + ); + } + } + } + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $args) { + list($serviceId, $method, $priority) = $args; + $listener = $this->container->get($serviceId); + + $key = $serviceId . '.' . $method; + if (!isset($this->listeners[$eventName][$key])) { + $this->addListener($eventName, array($listener, $method), $priority); + } + elseif ($listener !== $this->listeners[$eventName][$key]) { + parent::removeListener($eventName, array( + $this->listeners[$eventName][$key], + $method + )); + $this->addListener($eventName, array($listener, $method), $priority); + } + + $this->listeners[$eventName][$key] = $listener; + } + } + } +}