vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php line 41

Open in your IDE?
  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\DependencyInjection;
  11. use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
  12. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  13. use Symfony\Component\DependencyInjection\ContainerBuilder;
  14. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  15. use Symfony\Component\DependencyInjection\Reference;
  16. use Symfony\Component\EventDispatcher\EventDispatcher;
  17. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  18. use Symfony\Contracts\EventDispatcher\Event;
  19. /**
  20.  * Compiler pass to register tagged services for an event dispatcher.
  21.  */
  22. class RegisterListenersPass implements CompilerPassInterface
  23. {
  24.     protected $dispatcherService;
  25.     protected $listenerTag;
  26.     protected $subscriberTag;
  27.     protected $eventAliasesParameter;
  28.     private $hotPathEvents = [];
  29.     private $hotPathTagName 'container.hot_path';
  30.     private $noPreloadEvents = [];
  31.     private $noPreloadTagName 'container.no_preload';
  32.     public function __construct(string $dispatcherService 'event_dispatcher'string $listenerTag 'kernel.event_listener'string $subscriberTag 'kernel.event_subscriber'string $eventAliasesParameter 'event_dispatcher.event_aliases')
  33.     {
  34.         if (< \func_num_args()) {
  35.             trigger_deprecation('symfony/event-dispatcher''5.3''Configuring "%s" is deprecated.'__CLASS__);
  36.         }
  37.         $this->dispatcherService $dispatcherService;
  38.         $this->listenerTag $listenerTag;
  39.         $this->subscriberTag $subscriberTag;
  40.         $this->eventAliasesParameter $eventAliasesParameter;
  41.     }
  42.     /**
  43.      * @return $this
  44.      */
  45.     public function setHotPathEvents(array $hotPathEvents)
  46.     {
  47.         $this->hotPathEvents array_flip($hotPathEvents);
  48.         if (< \func_num_args()) {
  49.             trigger_deprecation('symfony/event-dispatcher''5.4''Configuring "$tagName" in "%s" is deprecated.'__METHOD__);
  50.             $this->hotPathTagName func_get_arg(1);
  51.         }
  52.         return $this;
  53.     }
  54.     /**
  55.      * @return $this
  56.      */
  57.     public function setNoPreloadEvents(array $noPreloadEvents): self
  58.     {
  59.         $this->noPreloadEvents array_flip($noPreloadEvents);
  60.         if (< \func_num_args()) {
  61.             trigger_deprecation('symfony/event-dispatcher''5.4''Configuring "$tagName" in "%s" is deprecated.'__METHOD__);
  62.             $this->noPreloadTagName func_get_arg(1);
  63.         }
  64.         return $this;
  65.     }
  66.     public function process(ContainerBuilder $container)
  67.     {
  68.         if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
  69.             return;
  70.         }
  71.         $aliases = [];
  72.         if ($container->hasParameter($this->eventAliasesParameter)) {
  73.             $aliases $container->getParameter($this->eventAliasesParameter);
  74.         }
  75.         $globalDispatcherDefinition $container->findDefinition($this->dispatcherService);
  76.         foreach ($container->findTaggedServiceIds($this->listenerTagtrue) as $id => $events) {
  77.             $noPreload 0;
  78.             foreach ($events as $event) {
  79.                 $priority $event['priority'] ?? 0;
  80.                 if (!isset($event['event'])) {
  81.                     if ($container->getDefinition($id)->hasTag($this->subscriberTag)) {
  82.                         continue;
  83.                     }
  84.                     $event['method'] = $event['method'] ?? '__invoke';
  85.                     $event['event'] = $this->getEventFromTypeDeclaration($container$id$event['method']);
  86.                 }
  87.                 $event['event'] = $aliases[$event['event']] ?? $event['event'];
  88.                 if (!isset($event['method'])) {
  89.                     $event['method'] = 'on'.preg_replace_callback([
  90.                         '/(?<=\b|_)[a-z]/i',
  91.                         '/[^a-z0-9]/i',
  92.                     ], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
  93.                     $event['method'] = preg_replace('/[^a-z0-9]/i'''$event['method']);
  94.                     if (null !== ($class $container->getDefinition($id)->getClass()) && ($r $container->getReflectionClass($classfalse)) && !$r->hasMethod($event['method'])) {
  95.                         if (!$r->hasMethod('__invoke')) {
  96.                             throw new InvalidArgumentException(sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "%s" tags.'$event['method'], $id$this->listenerTag));
  97.                         }
  98.                         $event['method'] = '__invoke';
  99.                     }
  100.                 }
  101.                 $dispatcherDefinition $globalDispatcherDefinition;
  102.                 if (isset($event['dispatcher'])) {
  103.                     $dispatcherDefinition $container->findDefinition($event['dispatcher']);
  104.                 }
  105.                 $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
  106.                 if (isset($this->hotPathEvents[$event['event']])) {
  107.                     $container->getDefinition($id)->addTag($this->hotPathTagName);
  108.                 } elseif (isset($this->noPreloadEvents[$event['event']])) {
  109.                     ++$noPreload;
  110.                 }
  111.             }
  112.             if ($noPreload && \count($events) === $noPreload) {
  113.                 $container->getDefinition($id)->addTag($this->noPreloadTagName);
  114.             }
  115.         }
  116.         $extractingDispatcher = new ExtractingEventDispatcher();
  117.         foreach ($container->findTaggedServiceIds($this->subscriberTagtrue) as $id => $tags) {
  118.             $def $container->getDefinition($id);
  119.             // We must assume that the class value has been correctly filled, even if the service is created by a factory
  120.             $class $def->getClass();
  121.             if (!$r $container->getReflectionClass($class)) {
  122.                 throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.'$class$id));
  123.             }
  124.             if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
  125.                 throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".'$idEventSubscriberInterface::class));
  126.             }
  127.             $class $r->name;
  128.             $dispatcherDefinitions = [];
  129.             foreach ($tags as $attributes) {
  130.                 if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) {
  131.                     continue;
  132.                 }
  133.                 $dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($attributes['dispatcher']);
  134.             }
  135.             if (!$dispatcherDefinitions) {
  136.                 $dispatcherDefinitions = [$globalDispatcherDefinition];
  137.             }
  138.             $noPreload 0;
  139.             ExtractingEventDispatcher::$aliases $aliases;
  140.             ExtractingEventDispatcher::$subscriber $class;
  141.             $extractingDispatcher->addSubscriber($extractingDispatcher);
  142.             foreach ($extractingDispatcher->listeners as $args) {
  143.                 $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
  144.                 foreach ($dispatcherDefinitions as $dispatcherDefinition) {
  145.                     $dispatcherDefinition->addMethodCall('addListener'$args);
  146.                 }
  147.                 if (isset($this->hotPathEvents[$args[0]])) {
  148.                     $container->getDefinition($id)->addTag($this->hotPathTagName);
  149.                 } elseif (isset($this->noPreloadEvents[$args[0]])) {
  150.                     ++$noPreload;
  151.                 }
  152.             }
  153.             if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
  154.                 $container->getDefinition($id)->addTag($this->noPreloadTagName);
  155.             }
  156.             $extractingDispatcher->listeners = [];
  157.             ExtractingEventDispatcher::$aliases = [];
  158.         }
  159.     }
  160.     private function getEventFromTypeDeclaration(ContainerBuilder $containerstring $idstring $method): string
  161.     {
  162.         if (
  163.             null === ($class $container->getDefinition($id)->getClass())
  164.             || !($r $container->getReflectionClass($classfalse))
  165.             || !$r->hasMethod($method)
  166.             || > ($m $r->getMethod($method))->getNumberOfParameters()
  167.             || !($type $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
  168.             || $type->isBuiltin()
  169.             || Event::class === ($name $type->getName())
  170.         ) {
  171.             throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.'$id$this->listenerTag));
  172.         }
  173.         return $name;
  174.     }
  175. }
  176. /**
  177.  * @internal
  178.  */
  179. class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
  180. {
  181.     public $listeners = [];
  182.     public static $aliases = [];
  183.     public static $subscriber;
  184.     public function addListener(string $eventName$listenerint $priority 0)
  185.     {
  186.         $this->listeners[] = [$eventName$listener[1], $priority];
  187.     }
  188.     public static function getSubscribedEvents(): array
  189.     {
  190.         $events = [];
  191.         foreach ([self::$subscriber'getSubscribedEvents']() as $eventName => $params) {
  192.             $events[self::$aliases[$eventName] ?? $eventName] = $params;
  193.         }
  194.         return $events;
  195.     }
  196. }