Chris@0:
Chris@0: *
Faster instantiation of the event dispatcher service
Chris@0: *
Chris@0: * Instead of calling addSubscriberService
once for each
Chris@0: * subscriber, a precompiled array of listener definitions is passed
Chris@0: * directly to the constructor. This is faster by roughly an order of
Chris@0: * magnitude. The listeners are collected and prepared using a compiler
Chris@0: * pass.
Chris@0: *
Chris@0: * Lazy instantiation of listeners
Chris@0: *
Chris@0: * Services are only retrieved from the container just before invocation.
Chris@0: * Especially when dispatching the KernelEvents::REQUEST event, this leads
Chris@0: * to a more timely invocation of the first listener. Overall dispatch
Chris@0: * runtime is not affected by this change though.
Chris@0: *
Chris@0: *
Chris@0: */
Chris@0: class ContainerAwareEventDispatcher implements EventDispatcherInterface {
Chris@0:
Chris@0: /**
Chris@0: * The service container.
Chris@0: *
Chris@17: * @var \Symfony\Component\DependencyInjection\ContainerInterface
Chris@0: */
Chris@0: protected $container;
Chris@0:
Chris@0: /**
Chris@0: * Listener definitions.
Chris@0: *
Chris@0: * A nested array of listener definitions keyed by event name and priority.
Chris@0: * A listener definition is an associative array with one of the following key
Chris@0: * value pairs:
Chris@0: * - callable: A callable listener
Chris@0: * - service: An array of the form [service id, method]
Chris@0: *
Chris@0: * A service entry will be resolved to a callable only just before its
Chris@0: * invocation.
Chris@0: *
Chris@0: * @var array
Chris@0: */
Chris@0: protected $listeners;
Chris@0:
Chris@0: /**
Chris@0: * Whether listeners need to be sorted prior to dispatch, keyed by event name.
Chris@0: *
Chris@0: * @var TRUE[]
Chris@0: */
Chris@0: protected $unsorted;
Chris@0:
Chris@0: /**
Chris@0: * Constructs a container aware event dispatcher.
Chris@0: *
Chris@0: * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
Chris@0: * The service container.
Chris@0: * @param array $listeners
Chris@0: * A nested array of listener definitions keyed by event name and priority.
Chris@0: * The array is expected to be ordered by priority. A listener definition is
Chris@0: * an associative array with one of the following key value pairs:
Chris@0: * - callable: A callable listener
Chris@0: * - service: An array of the form [service id, method]
Chris@0: * A service entry will be resolved to a callable only just before its
Chris@0: * invocation.
Chris@0: */
Chris@0: public function __construct(ContainerInterface $container, array $listeners = []) {
Chris@0: $this->container = $container;
Chris@0: $this->listeners = $listeners;
Chris@0: $this->unsorted = [];
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@0: public function dispatch($event_name, Event $event = NULL) {
Chris@0: if ($event === NULL) {
Chris@0: $event = new Event();
Chris@0: }
Chris@0:
Chris@0: if (isset($this->listeners[$event_name])) {
Chris@0: // Sort listeners if necessary.
Chris@0: if (isset($this->unsorted[$event_name])) {
Chris@0: krsort($this->listeners[$event_name]);
Chris@0: unset($this->unsorted[$event_name]);
Chris@0: }
Chris@0:
Chris@0: // Invoke listeners and resolve callables if necessary.
Chris@0: foreach ($this->listeners[$event_name] as $priority => &$definitions) {
Chris@0: foreach ($definitions as $key => &$definition) {
Chris@0: if (!isset($definition['callable'])) {
Chris@0: $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0: }
Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
Chris@14: $definition['callable'][0] = $definition['callable'][0]();
Chris@14: }
Chris@0:
Chris@14: call_user_func($definition['callable'], $event, $event_name, $this);
Chris@0: if ($event->isPropagationStopped()) {
Chris@0: return $event;
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: return $event;
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@0: public function getListeners($event_name = NULL) {
Chris@0: $result = [];
Chris@0:
Chris@0: if ($event_name === NULL) {
Chris@0: // If event name was omitted, collect all listeners of all events.
Chris@0: foreach (array_keys($this->listeners) as $event_name) {
Chris@0: $listeners = $this->getListeners($event_name);
Chris@0: if (!empty($listeners)) {
Chris@0: $result[$event_name] = $listeners;
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0: elseif (isset($this->listeners[$event_name])) {
Chris@0: // Sort listeners if necessary.
Chris@0: if (isset($this->unsorted[$event_name])) {
Chris@0: krsort($this->listeners[$event_name]);
Chris@0: unset($this->unsorted[$event_name]);
Chris@0: }
Chris@0:
Chris@0: // Collect listeners and resolve callables if necessary.
Chris@0: foreach ($this->listeners[$event_name] as $priority => &$definitions) {
Chris@0: foreach ($definitions as $key => &$definition) {
Chris@0: if (!isset($definition['callable'])) {
Chris@0: $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0: }
Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
Chris@14: $definition['callable'][0] = $definition['callable'][0]();
Chris@14: }
Chris@0:
Chris@0: $result[] = $definition['callable'];
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: return $result;
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@14: public function getListenerPriority($event_name, $listener) {
Chris@14: if (!isset($this->listeners[$event_name])) {
Chris@0: return;
Chris@0: }
Chris@14: if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
Chris@14: $listener[0] = $listener[0]();
Chris@0: }
Chris@0: // Resolve service definitions if the listener has not been found so far.
Chris@14: foreach ($this->listeners[$event_name] as $priority => &$definitions) {
Chris@0: foreach ($definitions as $key => &$definition) {
Chris@0: if (!isset($definition['callable'])) {
Chris@0: // Once the callable is retrieved we keep it for subsequent method
Chris@0: // invocations on this class.
Chris@14: $definition['callable'] = [
Chris@14: $this->container->get($definition['service'][0]),
Chris@14: $definition['service'][1],
Chris@14: ];
Chris@14: }
Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
Chris@14: $definition['callable'][0] = $definition['callable'][0]();
Chris@14: }
Chris@14: if ($definition['callable'] === $listener) {
Chris@14: return $priority;
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@0: public function hasListeners($event_name = NULL) {
Chris@14: if ($event_name !== NULL) {
Chris@14: return !empty($this->listeners[$event_name]);
Chris@14: }
Chris@14:
Chris@14: foreach ($this->listeners as $event_listeners) {
Chris@14: if ($event_listeners) {
Chris@14: return TRUE;
Chris@14: }
Chris@14: }
Chris@14:
Chris@14: return FALSE;
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@0: public function addListener($event_name, $listener, $priority = 0) {
Chris@0: $this->listeners[$event_name][$priority][] = ['callable' => $listener];
Chris@0: $this->unsorted[$event_name] = TRUE;
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@0: public function removeListener($event_name, $listener) {
Chris@0: if (!isset($this->listeners[$event_name])) {
Chris@0: return;
Chris@0: }
Chris@0:
Chris@0: foreach ($this->listeners[$event_name] as $priority => $definitions) {
Chris@0: foreach ($definitions as $key => $definition) {
Chris@0: if (!isset($definition['callable'])) {
Chris@0: if (!$this->container->initialized($definition['service'][0])) {
Chris@0: continue;
Chris@0: }
Chris@0: $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0: }
Chris@0:
Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure && !$listener instanceof \Closure) {
Chris@14: $definition['callable'][0] = $definition['callable'][0]();
Chris@14: }
Chris@14:
Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && !$definition['callable'][0] instanceof \Closure && is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
Chris@14: $listener[0] = $listener[0]();
Chris@14: }
Chris@0: if ($definition['callable'] === $listener) {
Chris@14: unset($definitions[$key]);
Chris@0: }
Chris@0: }
Chris@14: if ($definitions) {
Chris@14: $this->listeners[$event_name][$priority] = $definitions;
Chris@14: }
Chris@14: else {
Chris@14: unset($this->listeners[$event_name][$priority]);
Chris@14: }
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@0: public function addSubscriber(EventSubscriberInterface $subscriber) {
Chris@0: foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
Chris@0: if (is_string($params)) {
Chris@0: $this->addListener($event_name, [$subscriber, $params]);
Chris@0: }
Chris@0: elseif (is_string($params[0])) {
Chris@0: $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
Chris@0: }
Chris@0: else {
Chris@0: foreach ($params as $listener) {
Chris@0: $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@0: public function removeSubscriber(EventSubscriberInterface $subscriber) {
Chris@0: foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
Chris@0: if (is_array($params) && is_array($params[0])) {
Chris@0: foreach ($params as $listener) {
Chris@0: $this->removeListener($event_name, [$subscriber, $listener[0]]);
Chris@0: }
Chris@0: }
Chris@0: else {
Chris@0: $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);
Chris@0: }
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: }