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 39a953fdcb..81680abaa1 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 a1a0a391f0..3c1d803709 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 966a79a454..5a42dd348f 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 37d2c460e6..c847341cce 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); + $value->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); - } - } - - 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 cb6e8bc4ed..af6d6925d6 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 @@ class AutowirePass extends AbstractRecursivePass 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 @@ class AutowirePass extends AbstractRecursivePass 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 @@ class AutowirePass extends AbstractRecursivePass } } - $arguments = $this->autowireMethod($reflectionMethod, $arguments); + $arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes); if ($arguments !== $call[1]) { $this->methodCalls[$i][1] = $arguments; @@ -185,7 +190,7 @@ class AutowirePass extends AbstractRecursivePass * * @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 @@ class AutowirePass extends AbstractRecursivePass $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 15febf9f70..12d3b26f74 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -62,10 +62,10 @@ class PassConfig 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 bfdaa21fe7..48f7c0186e 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\ChildDefinition; 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\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; @@ -338,7 +336,7 @@ class IntegrationTest extends TestCase ->addTag('foo_bar', ['foo' => 'foo']) ; $container->register(IteratorConsumer::class) - ->setAutoconfigured(true) + ->setAutowired(true) ->setPublic(true) ; @@ -391,7 +389,7 @@ class IntegrationTest extends TestCase ->addTag('foo_bar', ['foo' => 'foo']) ; $container->register(LocatorConsumer::class) - ->setAutoconfigured(true) + ->setAutowired(true) ->setPublic(true) ; @@ -419,7 +417,7 @@ class IntegrationTest extends TestCase ->setPublic(true) ->setArguments([ (new Definition(LocatorConsumer::class)) - ->setAutoconfigured(true), + ->setAutowired(true), ]) ; @@ -445,7 +443,7 @@ class IntegrationTest extends TestCase $container->register(LocatorConsumerFactory::class); $container->register(LocatorConsumer::class) ->setPublic(true) - ->setAutoconfigured(true) + ->setAutowired(true) ->setFactory(new Reference(LocatorConsumerFactory::class)) ; @@ -458,22 +456,6 @@ class IntegrationTest extends TestCase 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 dcfb58b76c..329a14f393 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 db47966f3c..487cce16c0 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 ab45d3c658..4783e0cb60 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 f5c8d8d366..4442a6bc08 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 9bca4ed557..5cf39764b5 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 @@ class ProjectServiceContainer extends Container 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,