Commit 257b73e1 authored by catch's avatar catch

Issue #1972300 by znerol, chx, dawehner: Write a more scalable dispatcher.

parent 695afed5
......@@ -380,7 +380,7 @@ services:
class: Drupal\Core\Routing\CurrentRouteMatch
arguments: ['@request_stack']
event_dispatcher:
class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
class: Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher
arguments: ['@service_container']
controller_resolver:
class: Drupal\Core\Controller\ControllerResolver
......
<?php
/**
* @file
* Contains Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher
*/
namespace Drupal\Component\EventDispatcher;
use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* A performance optimized container aware event dispatcher.
*
* This version of the event dispatcher contains the following optimizations
* in comparison to the Symfony event dispatcher component:
*
* <dl>
* <dt>Faster instantiation of the event dispatcher service</dt>
* <dd>
* Instead of calling <code>addSubscriberService</code> once for each
* subscriber, a precompiled array of listener definitions is passed
* directly to the constructor. This is faster by roughly an order of
* magnitude. The listeners are collected and prepared using a compiler
* pass.
* </dd>
* <dt>Lazy instantiation of listeners</dt>
* <dd>
* Services are only retrieved from the container just before invocation.
* Especially when dispatching the KernelEvents::REQUEST event, this leads
* to a more timely invocation of the first listener. Overall dispatch
* runtime is not affected by this change though.
* </dd>
* </dl>
*/
class ContainerAwareEventDispatcher implements EventDispatcherInterface {
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
*/
protected $container;
/**
* Listener definitions.
*
* A nested array of listener definitions keyed by event name and priority.
* A listener definition is an associative array with one of the following key
* value pairs:
* - callable: A callable listener
* - service: An array of the form [service id, method]
*
* A service entry will be resolved to a callable only just before its
* invocation.
*
* @var array
*/
protected $listeners;
/**
* Whether listeners need to be sorted prior to dispatch, keyed by event name.
*
* @var TRUE[]
*/
protected $unsorted;
/**
* Constructs a container aware event dispatcher.
*
* @param \Symfony\Component\EventDispatcher\IntrospectableContainerInterface $container
* The service container.
* @param array $listeners
* A nested array of listener definitions keyed by event name and priority.
* The array is expected to be ordered by priority. A listener definition is
* an associative array with one of the following key value pairs:
* - callable: A callable listener
* - service: An array of the form [service id, method]
* A service entry will be resolved to a callable only just before its
* invocation.
*/
public function __construct(IntrospectableContainerInterface $container, array $listeners = []) {
$this->container = $container;
$this->listeners = $listeners;
$this->unsorted = [];
}
/**
* {@inheritdoc}
*/
public function dispatch($event_name, Event $event = NULL) {
if ($event === NULL) {
$event = new Event();
}
$event->setDispatcher($this);
$event->setName($event_name);
if (isset($this->listeners[$event_name])) {
// Sort listeners if necessary.
if (isset($this->unsorted[$event_name])) {
krsort($this->listeners[$event_name]);
unset($this->unsorted[$event_name]);
}
// Invoke listeners and resolve callables if necessary.
foreach ($this->listeners[$event_name] as $priority => &$definitions) {
foreach ($definitions as $key => &$definition) {
if (!isset($definition['callable'])) {
$definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
}
$definition['callable']($event, $event_name, $this);
if ($event->isPropagationStopped()) {
return $event;
}
}
}
}
return $event;
}
/**
* {@inheritdoc}
*/
public function getListeners($event_name = NULL) {
$result = [];
if ($event_name === NULL) {
// If event name was omitted, collect all listeners of all events.
foreach (array_keys($this->listeners) as $event_name) {
$listeners = $this->getListeners($event_name);
if (!empty($listeners)) {
$result[$event_name] = $listeners;
}
}
}
elseif (isset($this->listeners[$event_name])) {
// Sort listeners if necessary.
if (isset($this->unsorted[$event_name])) {
krsort($this->listeners[$event_name]);
unset($this->unsorted[$event_name]);
}
// Collect listeners and resolve callables if necessary.
foreach ($this->listeners[$event_name] as $priority => &$definitions) {
foreach ($definitions as $key => &$definition) {
if (!isset($definition['callable'])) {
$definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
}
$result[] = $definition['callable'];
}
}
}
return $result;
}
/**
* {@inheritdoc}
*/
public function hasListeners($event_name = NULL) {
return (bool) count($this->getListeners($event_name));
}
/**
* {@inheritdoc}
*/
public function addListener($event_name, $listener, $priority = 0) {
$this->listeners[$event_name][$priority][] = ['callable' => $listener];
$this->unsorted[$event_name] = TRUE;
}
/**
* {@inheritdoc}
*/
public function removeListener($event_name, $listener) {
if (!isset($this->listeners[$event_name])) {
return;
}
foreach ($this->listeners[$event_name] as $priority => $definitions) {
foreach ($definitions as $key => $definition) {
if (!isset($definition['callable'])) {
if (!$this->container->initialized($definition['service'][0])) {
continue;
}
$definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
}
if ($definition['callable'] === $listener) {
unset($this->listeners[$event_name][$priority][$key]);
}
}
}
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber) {
foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
if (is_string($params)) {
$this->addListener($event_name, array($subscriber, $params));
}
elseif (is_string($params[0])) {
$this->addListener($event_name, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
}
else {
foreach ($params as $listener) {
$this->addListener($event_name, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber) {
foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
if (is_array($params) && is_array($params[0])) {
foreach ($params as $listener) {
$this->removeListener($event_name, array($subscriber, $listener[0]));
}
}
else {
$this->removeListener($event_name, array($subscriber, is_string($params) ? $params : $params[0]));
}
}
}
}
......@@ -18,6 +18,7 @@ public function process(ContainerBuilder $container) {
$definition = $container->getDefinition('event_dispatcher');
$event_subscriber_info = [];
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 +29,30 @@ 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)) {
$priority = 0;
$event_subscriber_info[$event_name][$priority][] = ['service' => [$id, $params]];
}
elseif (is_string($params[0])) {
$priority = isset($params[1]) ? $params[1] : 0;
$event_subscriber_info[$event_name][$priority][] = ['service' => [$id, $params[0]]];
}
else {
foreach ($params as $listener) {
$priority = isset($listener[1]) ? $listener[1] : 0;
$event_subscriber_info[$event_name][$priority][] = ['service' => [$id, $listener[0]]];
}
}
}
}
foreach (array_keys($event_subscriber_info) as $event_name) {
krsort($event_subscriber_info[$event_name]);
}
$definition->addArgument($event_subscriber_info);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment