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

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;
  30.     private $noPreloadEvents = [];
  31.     private $noPreloadTagName;
  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 $hotPathEventsstring $tagName 'container.hot_path')
  46.     {
  47.         $this->hotPathEvents array_flip($hotPathEvents);
  48.         $this->hotPathTagName $tagName;
  49.         return $this;
  50.     }
  51.     /**
  52.      * @return $this
  53.      */
  54.     public function setNoPreloadEvents(array $noPreloadEventsstring $tagName 'container.no_preload'): self
  55.     {
  56.         $this->noPreloadEvents array_flip($noPreloadEvents);
  57.         $this->noPreloadTagName $tagName;
  58.         return $this;
  59.     }
  60.     public function process(ContainerBuilder $container)
  61.     {
  62.         if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
  63.             return;
  64.         }
  65.         $aliases = [];
  66.         if ($container->hasParameter($this->eventAliasesParameter)) {
  67.             $aliases $container->getParameter($this->eventAliasesParameter);
  68.         }
  69.         $globalDispatcherDefinition $container->findDefinition($this->dispatcherService);
  70.         foreach ($container->findTaggedServiceIds($this->listenerTagtrue) as $id => $events) {
  71.             $noPreload 0;
  72.             foreach ($events as $event) {
  73.                 $priority $event['priority'] ?? 0;
  74.                 if (!isset($event['event'])) {
  75.                     if ($container->getDefinition($id)->hasTag($this->subscriberTag)) {
  76.                         continue;
  77.                     }
  78.                     $event['method'] = $event['method'] ?? '__invoke';
  79.                     $event['event'] = $this->getEventFromTypeDeclaration($container$id$event['method']);
  80.                 }
  81.                 $event['event'] = $aliases[$event['event']] ?? $event['event'];
  82.                 if (!isset($event['method'])) {
  83.                     $event['method'] = 'on'.preg_replace_callback([
  84.                         '/(?<=\b|_)[a-z]/i',
  85.                         '/[^a-z0-9]/i',
  86.                     ], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
  87.                     $event['method'] = preg_replace('/[^a-z0-9]/i'''$event['method']);
  88.                     if (null !== ($class $container->getDefinition($id)->getClass()) && ($r $container->getReflectionClass($classfalse)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) {
  89.                         $event['method'] = '__invoke';
  90.                     }
  91.                 }
  92.                 $dispatcherDefinition $globalDispatcherDefinition;
  93.                 if (isset($event['dispatcher'])) {
  94.                     $dispatcherDefinition $container->getDefinition($event['dispatcher']);
  95.                 }
  96.                 $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
  97.                 if (isset($this->hotPathEvents[$event['event']])) {
  98.                     $container->getDefinition($id)->addTag($this->hotPathTagName);
  99.                 } elseif (isset($this->noPreloadEvents[$event['event']])) {
  100.                     ++$noPreload;
  101.                 }
  102.             }
  103.             if ($noPreload && \count($events) === $noPreload) {
  104.                 $container->getDefinition($id)->addTag($this->noPreloadTagName);
  105.             }
  106.         }
  107.         $extractingDispatcher = new ExtractingEventDispatcher();
  108.         foreach ($container->findTaggedServiceIds($this->subscriberTagtrue) as $id => $tags) {
  109.             $def $container->getDefinition($id);
  110.             // We must assume that the class value has been correctly filled, even if the service is created by a factory
  111.             $class $def->getClass();
  112.             if (!$r $container->getReflectionClass($class)) {
  113.                 throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.'$class$id));
  114.             }
  115.             if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
  116.                 throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".'$idEventSubscriberInterface::class));
  117.             }
  118.             $class $r->name;
  119.             $dispatcherDefinitions = [];
  120.             foreach ($tags as $attributes) {
  121.                 if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) {
  122.                     continue;
  123.                 }
  124.                 $dispatcherDefinitions[$attributes['dispatcher']] = $container->getDefinition($attributes['dispatcher']);
  125.             }
  126.             if (!$dispatcherDefinitions) {
  127.                 $dispatcherDefinitions = [$globalDispatcherDefinition];
  128.             }
  129.             $noPreload 0;
  130.             ExtractingEventDispatcher::$aliases $aliases;
  131.             ExtractingEventDispatcher::$subscriber $class;
  132.             $extractingDispatcher->addSubscriber($extractingDispatcher);
  133.             foreach ($extractingDispatcher->listeners as $args) {
  134.                 $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
  135.                 foreach ($dispatcherDefinitions as $dispatcherDefinition) {
  136.                     $dispatcherDefinition->addMethodCall('addListener'$args);
  137.                 }
  138.                 if (isset($this->hotPathEvents[$args[0]])) {
  139.                     $container->getDefinition($id)->addTag($this->hotPathTagName);
  140.                 } elseif (isset($this->noPreloadEvents[$args[0]])) {
  141.                     ++$noPreload;
  142.                 }
  143.             }
  144.             if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
  145.                 $container->getDefinition($id)->addTag($this->noPreloadTagName);
  146.             }
  147.             $extractingDispatcher->listeners = [];
  148.             ExtractingEventDispatcher::$aliases = [];
  149.         }
  150.     }
  151.     private function getEventFromTypeDeclaration(ContainerBuilder $containerstring $idstring $method): string
  152.     {
  153.         if (
  154.             null === ($class $container->getDefinition($id)->getClass())
  155.             || !($r $container->getReflectionClass($classfalse))
  156.             || !$r->hasMethod($method)
  157.             || > ($m $r->getMethod($method))->getNumberOfParameters()
  158.             || !($type $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
  159.             || $type->isBuiltin()
  160.             || Event::class === ($name $type->getName())
  161.         ) {
  162.             throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.'$id$this->listenerTag));
  163.         }
  164.         return $name;
  165.     }
  166. }
  167. /**
  168.  * @internal
  169.  */
  170. class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
  171. {
  172.     public $listeners = [];
  173.     public static $aliases = [];
  174.     public static $subscriber;
  175.     public function addListener(string $eventName$listenerint $priority 0)
  176.     {
  177.         $this->listeners[] = [$eventName$listener[1], $priority];
  178.     }
  179.     public static function getSubscribedEvents(): array
  180.     {
  181.         $events = [];
  182.         foreach ([self::$subscriber'getSubscribedEvents']() as $eventName => $params) {
  183.             $events[self::$aliases[$eventName] ?? $eventName] = $params;
  184.         }
  185.         return $events;
  186.     }
  187. }