From 3c7dbb4f134de26c7913f3f50c6f9ff98503102b Mon Sep 17 00:00:00 2001 From: Robert Meijers Date: Thu, 3 Nov 2022 18:26:28 +0100 Subject: [PATCH] [DependencyInjection] don't move locator tag for service subscriber Decorators move tags applied to the decorated service to the decorating service. But this (sometimes) breaks when the decorated service is a service subscriber, which has the argument for the container explicitly set. This mostly works because the locator for the service subscriber is applied twice. The RegisterServiceSubscriberPass which creates the locator also sets a binding on the service. The ResolveServiceSubscriberPass replaces the arguments referencing the ContainerInterface or ServiceProviderInterface for those services tagged with the container.service_subscriber.locator tag. So when the argument isn't provided in the service definition it will automatically be set using the binding. And in case the argument is set, it will be replaced by the Resolver pass based on the tag. But this thus breaks in case a service explicitly sets the argument (which means the binding isn't applied) and the service being decorated (meaning the locator tag is "lost"). So add the locator tag to the list of tags to keep on the original service. --- .../Compiler/DecoratorServicePass.php | 2 +- .../Compiler/DecoratorServicePassTest.php | 4 +-- .../Tests/Compiler/IntegrationTest.php | 33 ++++++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php index 3b8086d0931e6..185a097ebe20b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php @@ -42,7 +42,7 @@ public function process(ContainerBuilder $container) $tagsToKeep = $container->hasParameter('container.behavior_describing_tags') ? $container->getParameter('container.behavior_describing_tags') - : ['container.do_not_inline', 'container.service_locator', 'container.service_subscriber']; + : ['container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'container.service_subscriber.locator']; foreach ($definitions as [$id, $definition]) { $decoratedService = $definition->getDecoratedService(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php index 9a456335569d4..2ec9b41ba9f53 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php @@ -249,7 +249,7 @@ public function testProcessLeavesServiceSubscriberTagOnOriginalDefinition() $container = new ContainerBuilder(); $container ->register('foo') - ->setTags(['container.service_subscriber' => [], 'bar' => ['attr' => 'baz']]) + ->setTags(['container.service_subscriber' => [], 'container.service_subscriber.locator' => [], 'bar' => ['attr' => 'baz']]) ; $container ->register('baz') @@ -259,7 +259,7 @@ public function testProcessLeavesServiceSubscriberTagOnOriginalDefinition() $this->process($container); - $this->assertEquals(['container.service_subscriber' => []], $container->getDefinition('baz.inner')->getTags()); + $this->assertEquals(['container.service_subscriber' => [], 'container.service_subscriber.locator' => []], $container->getDefinition('baz.inner')->getTags()); $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar']], $container->getDefinition('baz')->getTags()); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 5a2b603d41f56..713f1b859ebfe 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; @@ -129,7 +130,7 @@ public function testProcessInlinesWhenThereAreMultipleReferencesButFromTheSameDe $this->assertFalse($container->hasDefinition('c'), 'Service C was not inlined.'); } - public function testCanDecorateServiceSubscriber() + public function testCanDecorateServiceSubscriberUsingBinding() { $container = new ContainerBuilder(); $container->register(ServiceSubscriberStub::class) @@ -137,11 +138,33 @@ public function testCanDecorateServiceSubscriber() ->setPublic(true); $container->register(DecoratedServiceSubscriber::class) + ->setProperty('inner', new Reference(DecoratedServiceSubscriber::class.'.inner')) ->setDecoratedService(ServiceSubscriberStub::class); $container->compile(); $this->assertInstanceOf(DecoratedServiceSubscriber::class, $container->get(ServiceSubscriberStub::class)); + $this->assertInstanceOf(ServiceSubscriberStub::class, $container->get(ServiceSubscriberStub::class)->inner); + $this->assertInstanceOf(ServiceLocator::class, $container->get(ServiceSubscriberStub::class)->inner->container); + } + + public function testCanDecorateServiceSubscriberReplacingArgument() + { + $container = new ContainerBuilder(); + $container->register(ServiceSubscriberStub::class) + ->setArguments([new Reference(ContainerInterface::class)]) + ->addTag('container.service_subscriber') + ->setPublic(true); + + $container->register(DecoratedServiceSubscriber::class) + ->setProperty('inner', new Reference(DecoratedServiceSubscriber::class.'.inner')) + ->setDecoratedService(ServiceSubscriberStub::class); + + $container->compile(); + + $this->assertInstanceOf(DecoratedServiceSubscriber::class, $container->get(ServiceSubscriberStub::class)); + $this->assertInstanceOf(ServiceSubscriberStub::class, $container->get(ServiceSubscriberStub::class)->inner); + $this->assertInstanceOf(ServiceLocator::class, $container->get(ServiceSubscriberStub::class)->inner->container); } public function testCanDecorateServiceLocator() @@ -515,6 +538,13 @@ public function testTaggedServiceLocatorWithDefaultIndex() class ServiceSubscriberStub implements ServiceSubscriberInterface { + public $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + public static function getSubscribedServices(): array { return []; @@ -523,6 +553,7 @@ public static function getSubscribedServices(): array class DecoratedServiceSubscriber { + public $inner; } class DecoratedServiceLocator implements ServiceProviderInterface