From b86aa3d06880ebdb1e3bb9059bb93e181cafe671 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 7 Mar 2021 16:10:22 +0100 Subject: [PATCH 1/2] [DependencyInjection] Bind constructor arguments via attributes --- .../Attribute/BindTaggedIterator.php | 22 +++ .../Attribute/BindTaggedLocator.php | 22 +++ .../DependencyInjection/CHANGELOG.md | 1 + .../AttributeAutoconfigurationPass.php | 94 ++++++++++--- .../Tests/Compiler/IntegrationTest.php | 131 ++++++++++++++++++ .../Tests/Fixtures/IteratorConsumer.php | 28 ++++ .../Tests/Fixtures/LocatorConsumer.php | 29 ++++ .../Fixtures/LocatorConsumerConsumer.php | 25 ++++ .../Tests/Fixtures/LocatorConsumerFactory.php | 25 ++++ .../Fixtures/MultipleArgumentBindings.php | 15 ++ 10 files changed, 376 insertions(+), 16 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Attribute/BindTaggedIterator.php create mode 100644 src/Symfony/Component/DependencyInjection/Attribute/BindTaggedLocator.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/IteratorConsumer.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumer.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerConsumer.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerFactory.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/MultipleArgumentBindings.php diff --git a/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedIterator.php b/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedIterator.php new file mode 100644 index 0000000000000..a1a0a391f0cb1 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedIterator.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class BindTaggedIterator +{ + public function __construct( + public string $tag, + public ?string $indexAttribute = null, + ) { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedLocator.php b/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedLocator.php new file mode 100644 index 0000000000000..39a953fdcb591 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedLocator.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class BindTaggedLocator +{ + public function __construct( + public string $tag, + public ?string $indexAttribute = null, + ) { + } +} diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index aa1ff13314352..966a79a454092 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 * Add `#[AsTaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators * Add autoconfigurable attributes + * Add support for binding tagged iterators and locators to constructor arguments via attributes * Add support for per-env configuration in loaders * Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration * Add support an integer return value for default_index_method diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php index aefa7c0c6176b..37d2c460e6ad9 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php @@ -11,40 +11,102 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Attribute\BindTaggedIterator; +use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; /** * @author Alexander M. Turek */ -final class AttributeAutoconfigurationPass implements CompilerPassInterface +final class AttributeAutoconfigurationPass extends AbstractRecursivePass { + /** @var array|null */ + private $argumentConfigurators; + public function process(ContainerBuilder $container): void { if (80000 > \PHP_VERSION_ID) { return; } - $autoconfiguredAttributes = $container->getAutoconfiguredAttributes(); + $this->argumentConfigurators = [ + BindTaggedIterator::class => static function (BindTaggedIterator $attribute) { + return new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute); + }, + BindTaggedLocator::class => static function (BindTaggedLocator $attribute) { + return new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute)); + }, + ]; + + parent::process($container); + + $this->argumentConfigurators = null; + } + + protected function processValue($value, bool $isRoot = false) + { + if ($value instanceof Definition + && $value->isAutoconfigured() + && !$value->isAbstract() + && !$value->hasTag('container.ignore_attributes') + ) { + $value = $this->processDefinition($value); + } + + return parent::processValue($value, $isRoot); + } + + private function processDefinition(Definition $definition): Definition + { + if (!$reflector = $this->container->getReflectionClass($definition->getClass(), false)) { + return $definition; + } - foreach ($container->getDefinitions() as $id => $definition) { - if (!$definition->isAutoconfigured() - || $definition->isAbstract() - || $definition->hasTag('container.ignore_attributes') - || !($reflector = $container->getReflectionClass($definition->getClass(), false)) - ) { - continue; + $autoconfiguredAttributes = $this->container->getAutoconfiguredAttributes(); + + $instanceof = $definition->getInstanceofConditionals(); + $conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition(''); + foreach ($reflector->getAttributes() as $attribute) { + if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) { + $configurator($conditionals, $attribute->newInstance(), $reflector); } + } + + if ($constructor = $this->getConstructor($definition, false)) { + $definition = $this->bindArguments($definition, $constructor); + } + + $instanceof[$reflector->getName()] = $conditionals; + $definition->setInstanceofConditionals($instanceof); - $instanceof = $definition->getInstanceofConditionals(); - $conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition(''); - foreach ($reflector->getAttributes() as $attribute) { - if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) { - $configurator($conditionals, $attribute->newInstance(), $reflector); + return $definition; + } + + private function bindArguments(Definition $definition, \ReflectionFunctionAbstract $constructor): Definition + { + $bindings = $definition->getBindings(); + foreach ($constructor->getParameters() as $reflectionParameter) { + $argument = null; + foreach ($reflectionParameter->getAttributes() as $attribute) { + if (!$configurator = $this->argumentConfigurators[$attribute->getName()] ?? null) { + continue; + } + if ($argument) { + throw new LogicException(sprintf('Cannot autoconfigure argument "$%s": More than one autoconfigurable attribute found.', $reflectionParameter->getName())); } + $argument = $configurator($attribute->newInstance()); + } + if ($argument) { + $bindings['$'.$reflectionParameter->getName()] = new BoundArgument($argument); } - $instanceof[$reflector->getName()] = $conditionals; - $definition->setInstanceofConditionals($instanceof); } + + return $definition->setBindings($bindings); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index aaaec478ac5a1..bfdaa21fe7f0c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; @@ -28,6 +29,11 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass; +use Symfony\Component\DependencyInjection\Tests\Fixtures\IteratorConsumer; +use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumer; +use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerConsumer; +use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerFactory; +use Symfony\Component\DependencyInjection\Tests\Fixtures\MultipleArgumentBindings; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3; @@ -317,6 +323,33 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod() $this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param); } + /** + * @requires PHP 8 + */ + public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredViaAttribute() + { + $container = new ContainerBuilder(); + $container->register(BarTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'bar_tab_class_with_defaultmethod']) + ; + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'foo']) + ; + $container->register(IteratorConsumer::class) + ->setAutoconfigured(true) + ->setPublic(true) + ; + + $container->compile(); + + $s = $container->get(IteratorConsumer::class); + + $param = iterator_to_array($s->getParam()->getIterator()); + $this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param); + } + public function testTaggedIteratorWithMultipleIndexAttribute() { $container = new ContainerBuilder(); @@ -343,6 +376,104 @@ public function testTaggedIteratorWithMultipleIndexAttribute() $this->assertSame(['bar' => $container->get(BarTagClass::class), 'bar_duplicate' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $param); } + /** + * @requires PHP 8 + */ + public function testTaggedLocatorConfiguredViaAttribute() + { + $container = new ContainerBuilder(); + $container->register(BarTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'bar_tab_class_with_defaultmethod']) + ; + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'foo']) + ; + $container->register(LocatorConsumer::class) + ->setAutoconfigured(true) + ->setPublic(true) + ; + + $container->compile(); + + /** @var LocatorConsumer $s */ + $s = $container->get(LocatorConsumer::class); + + $locator = $s->getLocator(); + self::assertSame($container->get(BarTagClass::class), $locator->get('bar_tab_class_with_defaultmethod')); + self::assertSame($container->get(FooTagClass::class), $locator->get('foo')); + } + + /** + * @requires PHP 8 + */ + public function testNestedDefinitionWithAutoconfiguredConstructorArgument() + { + $container = new ContainerBuilder(); + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'foo']) + ; + $container->register(LocatorConsumerConsumer::class) + ->setPublic(true) + ->setArguments([ + (new Definition(LocatorConsumer::class)) + ->setAutoconfigured(true), + ]) + ; + + $container->compile(); + + /** @var LocatorConsumerConsumer $s */ + $s = $container->get(LocatorConsumerConsumer::class); + + $locator = $s->getLocatorConsumer()->getLocator(); + self::assertSame($container->get(FooTagClass::class), $locator->get('foo')); + } + + /** + * @requires PHP 8 + */ + public function testFactoryWithAutoconfiguredArgument() + { + $container = new ContainerBuilder(); + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['key' => 'my_service']) + ; + $container->register(LocatorConsumerFactory::class); + $container->register(LocatorConsumer::class) + ->setPublic(true) + ->setAutoconfigured(true) + ->setFactory(new Reference(LocatorConsumerFactory::class)) + ; + + $container->compile(); + + /** @var LocatorConsumer $s */ + $s = $container->get(LocatorConsumer::class); + + $locator = $s->getLocator(); + self::assertSame($container->get(FooTagClass::class), $locator->get('my_service')); + } + + /** + * @requires PHP 8 + */ + public function testMultipleArgumentBindings() + { + $container = new ContainerBuilder(); + $container->register(MultipleArgumentBindings::class) + ->setPublic(true) + ->setAutoconfigured(true) + ; + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot autoconfigure argument "$collection": More than one autoconfigurable attribute found.'); + $container->compile(); + } + public function testTaggedServiceWithDefaultPriorityMethod() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/IteratorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/IteratorConsumer.php new file mode 100644 index 0000000000000..dcfb58b76c60d --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/IteratorConsumer.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Attribute\BindTaggedIterator; + +final class IteratorConsumer +{ + public function __construct( + #[BindTaggedIterator('foo_bar', indexAttribute: 'foo')] + private iterable $param, + ) { + } + + public function getParam(): iterable + { + return $this->param; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumer.php new file mode 100644 index 0000000000000..db47966f3cea8 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumer.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator; + +final class LocatorConsumer +{ + public function __construct( + #[BindTaggedLocator('foo_bar', indexAttribute: 'foo')] + private ContainerInterface $locator, + ) { + } + + public function getLocator(): ContainerInterface + { + return $this->locator; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerConsumer.php new file mode 100644 index 0000000000000..c686754c5ad7e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerConsumer.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +final class LocatorConsumerConsumer +{ + public function __construct( + private LocatorConsumer $locatorConsumer + ) { + } + + public function getLocatorConsumer(): LocatorConsumer + { + return $this->locatorConsumer; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerFactory.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerFactory.php new file mode 100644 index 0000000000000..ab45d3c658667 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerFactory.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator; + +final class LocatorConsumerFactory +{ + public function __invoke( + #[BindTaggedLocator('foo_bar', indexAttribute: 'key')] + ContainerInterface $locator + ): LocatorConsumer { + return new LocatorConsumer($locator); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/MultipleArgumentBindings.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/MultipleArgumentBindings.php new file mode 100644 index 0000000000000..f5c8d8d366c9b --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/MultipleArgumentBindings.php @@ -0,0 +1,15 @@ + Date: Sun, 11 Apr 2021 22:50:06 +0200 Subject: [PATCH 2/2] Autowire arguments using attributes --- ...ndTaggedLocator.php => TaggedIterator.php} | 2 +- ...ndTaggedIterator.php => TaggedLocator.php} | 2 +- .../DependencyInjection/CHANGELOG.md | 2 +- .../AttributeAutoconfigurationPass.php | 76 +++---------------- .../Compiler/AutowirePass.php | 33 +++++++- .../Compiler/PassConfig.php | 2 +- .../Tests/Compiler/IntegrationTest.php | 26 +------ .../Tests/Fixtures/IteratorConsumer.php | 4 +- .../Tests/Fixtures/LocatorConsumer.php | 4 +- .../Tests/Fixtures/LocatorConsumerFactory.php | 4 +- .../Fixtures/MultipleArgumentBindings.php | 6 +- .../Fixtures/php/services_subscriber.php | 1 + 12 files changed, 57 insertions(+), 105 deletions(-) rename src/Symfony/Component/DependencyInjection/Attribute/{BindTaggedLocator.php => TaggedIterator.php} (95%) rename src/Symfony/Component/DependencyInjection/Attribute/{BindTaggedIterator.php => TaggedLocator.php} (94%) diff --git a/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedLocator.php b/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php similarity index 95% rename from src/Symfony/Component/DependencyInjection/Attribute/BindTaggedLocator.php rename to src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php index 39a953fdcb591..81680abaa1849 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedLocator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php @@ -12,7 +12,7 @@ namespace Symfony\Component\DependencyInjection\Attribute; #[\Attribute(\Attribute::TARGET_PARAMETER)] -class BindTaggedLocator +class TaggedIterator { public function __construct( public string $tag, diff --git a/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedIterator.php b/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php similarity index 94% rename from src/Symfony/Component/DependencyInjection/Attribute/BindTaggedIterator.php rename to src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php index a1a0a391f0cb1..3c1d8037093a0 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/BindTaggedIterator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php @@ -12,7 +12,7 @@ namespace Symfony\Component\DependencyInjection\Attribute; #[\Attribute(\Attribute::TARGET_PARAMETER)] -class BindTaggedIterator +class TaggedLocator { public function __construct( public string $tag, diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 966a79a454092..5a42dd348fd30 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -9,7 +9,7 @@ CHANGELOG * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 * Add `#[AsTaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators * Add autoconfigurable attributes - * Add support for binding tagged iterators and locators to constructor arguments via attributes + * Add support for autowiring tagged iterators and locators via attributes on PHP 8 * Add support for per-env configuration in loaders * Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration * Add support an integer return value for default_index_method diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php index 37d2c460e6ad9..c847341cce16f 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php @@ -11,102 +11,46 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\BoundArgument; -use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; -use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; -use Symfony\Component\DependencyInjection\Attribute\BindTaggedIterator; -use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Exception\LogicException; /** * @author Alexander M. Turek */ final class AttributeAutoconfigurationPass extends AbstractRecursivePass { - /** @var array|null */ - private $argumentConfigurators; - public function process(ContainerBuilder $container): void { - if (80000 > \PHP_VERSION_ID) { + if (80000 > \PHP_VERSION_ID || !$container->getAutoconfiguredAttributes()) { return; } - $this->argumentConfigurators = [ - BindTaggedIterator::class => static function (BindTaggedIterator $attribute) { - return new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute); - }, - BindTaggedLocator::class => static function (BindTaggedLocator $attribute) { - return new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute)); - }, - ]; - parent::process($container); - - $this->argumentConfigurators = null; } protected function processValue($value, bool $isRoot = false) { - if ($value instanceof Definition - && $value->isAutoconfigured() - && !$value->isAbstract() - && !$value->hasTag('container.ignore_attributes') + if (!$value instanceof Definition + || !$value->isAutoconfigured() + || $value->isAbstract() + || $value->hasTag('container.ignore_attributes') + || !($reflector = $this->container->getReflectionClass($value->getClass(), false)) ) { - $value = $this->processDefinition($value); - } - - return parent::processValue($value, $isRoot); - } - - private function processDefinition(Definition $definition): Definition - { - if (!$reflector = $this->container->getReflectionClass($definition->getClass(), false)) { - return $definition; + return parent::processValue($value, $isRoot); } $autoconfiguredAttributes = $this->container->getAutoconfiguredAttributes(); - - $instanceof = $definition->getInstanceofConditionals(); + $instanceof = $value->getInstanceofConditionals(); $conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition(''); foreach ($reflector->getAttributes() as $attribute) { if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) { $configurator($conditionals, $attribute->newInstance(), $reflector); } } - - if ($constructor = $this->getConstructor($definition, false)) { - $definition = $this->bindArguments($definition, $constructor); - } - $instanceof[$reflector->getName()] = $conditionals; - $definition->setInstanceofConditionals($instanceof); - - return $definition; - } - - private function bindArguments(Definition $definition, \ReflectionFunctionAbstract $constructor): Definition - { - $bindings = $definition->getBindings(); - foreach ($constructor->getParameters() as $reflectionParameter) { - $argument = null; - foreach ($reflectionParameter->getAttributes() as $attribute) { - if (!$configurator = $this->argumentConfigurators[$attribute->getName()] ?? null) { - continue; - } - if ($argument) { - throw new LogicException(sprintf('Cannot autoconfigure argument "$%s": More than one autoconfigurable attribute found.', $reflectionParameter->getName())); - } - $argument = $configurator($attribute->newInstance()); - } - if ($argument) { - $bindings['$'.$reflectionParameter->getName()] = new BoundArgument($argument); - } - } + $value->setInstanceofConditionals($instanceof); - return $definition->setBindings($bindings); + return parent::processValue($value, $isRoot); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index cb6e8bc4ed1b3..af6d6925d6e08 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -12,6 +12,10 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; +use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; @@ -123,7 +127,8 @@ private function doProcessValue($value, bool $isRoot = false) array_unshift($this->methodCalls, [$constructor, $value->getArguments()]); } - $this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot); + $checkAttributes = 80000 <= \PHP_VERSION_ID && !$value->hasTag('container.ignore_attributes'); + $this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot, $checkAttributes); if ($constructor) { [, $arguments] = array_shift($this->methodCalls); @@ -140,7 +145,7 @@ private function doProcessValue($value, bool $isRoot = false) return $value; } - private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot): array + private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array { $this->decoratedId = null; $this->decoratedClass = null; @@ -168,7 +173,7 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot): } } - $arguments = $this->autowireMethod($reflectionMethod, $arguments); + $arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes); if ($arguments !== $call[1]) { $this->methodCalls[$i][1] = $arguments; @@ -185,7 +190,7 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot): * * @throws AutowiringFailedException */ - private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments): array + private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes): array { $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId; $method = $reflectionMethod->name; @@ -201,6 +206,26 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true); + if ($checkAttributes) { + foreach ($parameter->getAttributes() as $attribute) { + if (TaggedIterator::class === $attribute->getName()) { + $attribute = $attribute->newInstance(); + $arguments[$index] = new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute); + break; + } + + if (TaggedLocator::class === $attribute->getName()) { + $attribute = $attribute->newInstance(); + $arguments[$index] = new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute)); + break; + } + } + + if ('' !== ($arguments[$index] ?? '')) { + continue; + } + } + if (!$type) { if (isset($arguments[$index])) { continue; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 15febf9f70fd1..12d3b26f740df 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -62,10 +62,10 @@ public function __construct() new AutowireRequiredMethodsPass(), new AutowireRequiredPropertiesPass(), new ResolveBindingsPass(), - new ServiceLocatorTagPass(), new DecoratorServicePass(), new CheckDefinitionValidityPass(), new AutowirePass(false), + new ServiceLocatorTagPass(), new ResolveTaggedIteratorArgumentPass(), new ResolveServiceSubscribersPass(), new ResolveReferencesToAliasesPass(), diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index bfdaa21fe7f0c..48f7c0186e2b4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -20,7 +20,6 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; @@ -33,7 +32,6 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumer; use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerConsumer; use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerFactory; -use Symfony\Component\DependencyInjection\Tests\Fixtures\MultipleArgumentBindings; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3; @@ -338,7 +336,7 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredVia ->addTag('foo_bar', ['foo' => 'foo']) ; $container->register(IteratorConsumer::class) - ->setAutoconfigured(true) + ->setAutowired(true) ->setPublic(true) ; @@ -391,7 +389,7 @@ public function testTaggedLocatorConfiguredViaAttribute() ->addTag('foo_bar', ['foo' => 'foo']) ; $container->register(LocatorConsumer::class) - ->setAutoconfigured(true) + ->setAutowired(true) ->setPublic(true) ; @@ -419,7 +417,7 @@ public function testNestedDefinitionWithAutoconfiguredConstructorArgument() ->setPublic(true) ->setArguments([ (new Definition(LocatorConsumer::class)) - ->setAutoconfigured(true), + ->setAutowired(true), ]) ; @@ -445,7 +443,7 @@ public function testFactoryWithAutoconfiguredArgument() $container->register(LocatorConsumerFactory::class); $container->register(LocatorConsumer::class) ->setPublic(true) - ->setAutoconfigured(true) + ->setAutowired(true) ->setFactory(new Reference(LocatorConsumerFactory::class)) ; @@ -458,22 +456,6 @@ public function testFactoryWithAutoconfiguredArgument() self::assertSame($container->get(FooTagClass::class), $locator->get('my_service')); } - /** - * @requires PHP 8 - */ - public function testMultipleArgumentBindings() - { - $container = new ContainerBuilder(); - $container->register(MultipleArgumentBindings::class) - ->setPublic(true) - ->setAutoconfigured(true) - ; - - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Cannot autoconfigure argument "$collection": More than one autoconfigurable attribute found.'); - $container->compile(); - } - public function testTaggedServiceWithDefaultPriorityMethod() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/IteratorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/IteratorConsumer.php index dcfb58b76c60d..329a14f39331d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/IteratorConsumer.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/IteratorConsumer.php @@ -11,12 +11,12 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; -use Symfony\Component\DependencyInjection\Attribute\BindTaggedIterator; +use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class IteratorConsumer { public function __construct( - #[BindTaggedIterator('foo_bar', indexAttribute: 'foo')] + #[TaggedIterator('foo_bar', indexAttribute: 'foo')] private iterable $param, ) { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumer.php index db47966f3cea8..487cce16c0da8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumer.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumer.php @@ -12,12 +12,12 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator; +use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; final class LocatorConsumer { public function __construct( - #[BindTaggedLocator('foo_bar', indexAttribute: 'foo')] + #[TaggedLocator('foo_bar', indexAttribute: 'foo')] private ContainerInterface $locator, ) { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerFactory.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerFactory.php index ab45d3c658667..4783e0cb609a2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerFactory.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LocatorConsumerFactory.php @@ -12,12 +12,12 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator; +use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; final class LocatorConsumerFactory { public function __invoke( - #[BindTaggedLocator('foo_bar', indexAttribute: 'key')] + #[TaggedLocator('foo_bar', indexAttribute: 'key')] ContainerInterface $locator ): LocatorConsumer { return new LocatorConsumer($locator); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/MultipleArgumentBindings.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/MultipleArgumentBindings.php index f5c8d8d366c9b..4442a6bc08c74 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/MultipleArgumentBindings.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/MultipleArgumentBindings.php @@ -2,13 +2,13 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; -use Symfony\Component\DependencyInjection\Attribute\BindTaggedIterator; -use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator; +use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; +use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; final class MultipleArgumentBindings { public function __construct( - #[BindTaggedIterator('my_tag'), BindTaggedLocator('another_tag')] + #[TaggedIterator('my_tag'), TaggedLocator('another_tag')] object $collection ) { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php index 9bca4ed5578de..5cf39764b51cb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php @@ -46,6 +46,7 @@ public function getRemovedIds(): array return [ '.service_locator.DlIAmAe' => true, '.service_locator.DlIAmAe.foo_service' => true, + '.service_locator.t5IGRMW' => true, 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true,