+ */ +class ResolveNoPreloadPass extends AbstractRecursivePass +{ + private const DO_PRELOAD_TAG = '.container.do_preload'; + + private $tagName; + private $resolvedIds = []; + + public function __construct(string $tagName = 'container.no_preload') + { + $this->tagName = $tagName; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $this->container = $container; + + try { + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isPublic() && !$definition->isPrivate() && !isset($this->resolvedIds[$id])) { + $this->resolvedIds[$id] = true; + $this->processValue($definition, true); + } + } + + foreach ($container->getAliases() as $alias) { + if ($alias->isPublic() && !$alias->isPrivate() && !isset($this->resolvedIds[$id = (string) $alias]) && $container->has($id)) { + $this->resolvedIds[$id] = true; + $this->processValue($container->getDefinition($id), true); + } + } + } finally { + $this->resolvedIds = []; + $this->container = null; + } + + foreach ($container->getDefinitions() as $definition) { + if ($definition->hasTag(self::DO_PRELOAD_TAG)) { + $definition->clearTag(self::DO_PRELOAD_TAG); + } elseif (!$definition->isDeprecated() && !$definition->hasErrors()) { + $definition->addTag($this->tagName); + } + } + } + + /** + * {@inheritdoc} + */ + protected function processValue($value, bool $isRoot = false) + { + if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->has($id = (string) $value)) { + $definition = $this->container->findDefinition($id); + + if (!isset($this->resolvedIds[$id])) { + $this->resolvedIds[$id] = true; + $this->processValue($definition, true); + } + + return $value; + } + + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + + if ($value->hasTag($this->tagName) || $value->isDeprecated() || $value->hasErrors()) { + return $value; + } + + if ($isRoot) { + $value->addTag(self::DO_PRELOAD_TAG); + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveNoPreloadPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveNoPreloadPassTest.php new file mode 100644 index 0000000000..7dbdbafd5a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveNoPreloadPassTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\ResolveNoPreloadPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class ResolveNoPreloadPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + + $container->register('entry_point') + ->setPublic(true) + ->addArgument(new Reference('preloaded')) + ->addArgument(new Reference('not_preloaded')); + + $container->register('preloaded') + ->addArgument(new Reference('preloaded_dep')) + ->addArgument(new Reference('common_dep')); + + $container->register('not_preloaded') + ->setPublic(true) + ->addTag('container.no_preload') + ->addArgument(new Reference('not_preloaded_dep')) + ->addArgument(new Reference('common_dep')); + + $container->register('preloaded_dep'); + $container->register('not_preloaded_dep'); + $container->register('common_dep'); + + (new ResolveNoPreloadPass())->process($container); + + $this->assertFalse($container->getDefinition('entry_point')->hasTag('container.no_preload')); + $this->assertFalse($container->getDefinition('preloaded')->hasTag('container.no_preload')); + $this->assertFalse($container->getDefinition('preloaded_dep')->hasTag('container.no_preload')); + $this->assertFalse($container->getDefinition('common_dep')->hasTag('container.no_preload')); + $this->assertTrue($container->getDefinition('not_preloaded')->hasTag('container.no_preload')); + $this->assertTrue($container->getDefinition('not_preloaded_dep')->hasTag('container.no_preload')); + } +} diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php index 02ca7caa13..5147e573ec 100644 --- a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php +++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php @@ -32,6 +32,8 @@ class RegisterListenersPass implements CompilerPassInterface private $hotPathEvents = []; private $hotPathTagName; + private $noPreloadEvents = []; + private $noPreloadTagName; public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases') { @@ -41,7 +43,10 @@ class RegisterListenersPass implements CompilerPassInterface $this->eventAliasesParameter = $eventAliasesParameter; } - public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path') + /** + * @return $this + */ + public function setHotPathEvents(array $hotPathEvents, string $tagName = 'container.hot_path') { $this->hotPathEvents = array_flip($hotPathEvents); $this->hotPathTagName = $tagName; @@ -49,6 +54,17 @@ class RegisterListenersPass implements CompilerPassInterface return $this; } + /** + * @return $this + */ + public function setNoPreloadEvents(array $noPreloadEvents, string $tagName = 'container.no_preload'): self + { + $this->noPreloadEvents = array_flip($noPreloadEvents); + $this->noPreloadTagName = $tagName; + + return $this; + } + public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { @@ -64,6 +80,8 @@ class RegisterListenersPass implements CompilerPassInterface $globalDispatcherDefinition = $container->findDefinition($this->dispatcherService); foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { + $noPreload = 0; + foreach ($events as $event) { $priority = isset($event['priority']) ? $event['priority'] : 0; @@ -99,8 +117,14 @@ class RegisterListenersPass implements CompilerPassInterface if (isset($this->hotPathEvents[$event['event']])) { $container->getDefinition($id)->addTag($this->hotPathTagName); + } elseif (isset($this->noPreloadEvents[$event['event']])) { + ++$noPreload; } } + + if ($noPreload && \count($events) === $noPreload) { + $container->getDefinition($id)->addTag($this->noPreloadTagName); + } } $extractingDispatcher = new ExtractingEventDispatcher(); @@ -132,6 +156,7 @@ class RegisterListenersPass implements CompilerPassInterface $dispatcherDefinitions = [$globalDispatcherDefinition]; } + $noPreload = 0; ExtractingEventDispatcher::$aliases = $aliases; ExtractingEventDispatcher::$subscriber = $class; $extractingDispatcher->addSubscriber($extractingDispatcher); @@ -143,8 +168,13 @@ class RegisterListenersPass implements CompilerPassInterface if (isset($this->hotPathEvents[$args[0]])) { $container->getDefinition($id)->addTag($this->hotPathTagName); + } elseif (isset($this->noPreloadEvents[$args[0]])) { + ++$noPreload; } } + if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) { + $container->getDefinition($id)->addTag($this->noPreloadTagName); + } $extractingDispatcher->listeners = []; ExtractingEventDispatcher::$aliases = []; } diff --git a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php index 5252664a9f..16aade0bc0 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -157,6 +157,27 @@ class RegisterListenersPassTest extends TestCase $this->assertTrue($container->getDefinition('foo')->hasTag('container.hot_path')); } + public function testNoPreloadEvents() + { + $container = new ContainerBuilder(); + + $container->register('foo', SubscriberService::class)->addTag('kernel.event_subscriber', []); + $container->register('bar')->addTag('kernel.event_listener', ['event' => 'cold_event']); + $container->register('baz') + ->addTag('kernel.event_listener', ['event' => 'event']) + ->addTag('kernel.event_listener', ['event' => 'cold_event']); + $container->register('event_dispatcher', 'stdClass'); + + (new RegisterListenersPass()) + ->setHotPathEvents(['event']) + ->setNoPreloadEvents(['cold_event']) + ->process($container); + + $this->assertFalse($container->getDefinition('foo')->hasTag('container.no_preload')); + $this->assertTrue($container->getDefinition('bar')->hasTag('container.no_preload')); + $this->assertFalse($container->getDefinition('baz')->hasTag('container.no_preload')); + } + public function testEventSubscriberUnresolvableClassName() { $this->expectException('InvalidArgumentException');