From 78018deb147e66f99d551d24e77ed63509c38df0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 3 Oct 2023 22:07:33 +0200 Subject: [PATCH 1/2] [DependencyInjection] Add `#[AutowireIterator]` attribute and improve `#[AutowireLocator]` --- .../Attribute/AutowireIterator.php | 77 +++++++++++++++++++ .../Attribute/AutowireLocator.php | 48 +++++++----- .../Attribute/TaggedIterator.php | 6 +- .../Attribute/TaggedLocator.php | 7 +- .../DependencyInjection/CHANGELOG.md | 2 +- .../RegisterServiceSubscribersPass.php | 7 +- .../Tests/Attribute/AutowireLocatorTest.php | 20 ++--- .../Fixtures/AutowireLocatorConsumer.php | 8 +- ...sterControllerArgumentLocatorsPassTest.php | 2 +- 9 files changed, 129 insertions(+), 48 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Attribute/AutowireIterator.php diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireIterator.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireIterator.php new file mode 100644 index 0000000000000..c40e4dc98d665 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireIterator.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\Attribute\SubscribedService; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * Autowires an iterator of services based on a tag name or an explicit list of key => service-type pairs. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireIterator extends Autowire +{ + /** + * @see ServiceSubscriberInterface::getSubscribedServices() + * + * @param string|array $services A tag name or an explicit list of services + * @param string|string[] $exclude A service or a list of services to exclude + */ + public function __construct( + string|array $services, + string $indexAttribute = null, + string $defaultIndexMethod = null, + string $defaultPriorityMethod = null, + string|array $exclude = [], + bool $excludeSelf = true, + ) { + if (\is_string($services)) { + parent::__construct(new TaggedIteratorArgument($services, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf)); + + return; + } + + $references = []; + + foreach ($services as $key => $type) { + $attributes = []; + + if ($type instanceof SubscribedService) { + $key = $type->key ?? $key; + $attributes = $type->attributes; + $type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s" is used, a type must be set.', SubscribedService::class))); + } + + if (!\is_string($type) || !preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { + throw new InvalidArgumentException(sprintf('"%s" is not a PHP type for key "%s".', \is_string($type) ? $type : get_debug_type($type), $key)); + } + $optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if ('?' === $type[0]) { + $type = substr($type, 1); + $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } + if (\is_int($name = $key)) { + $key = $type; + $name = null; + } + + $references[$key] = new TypedReference($type, $type, $optionalBehavior, $name, $attributes); + } + + parent::__construct(new IteratorArgument($references)); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php index cae2d52a8b1cf..e1a570ad7f091 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php @@ -11,32 +11,40 @@ namespace Symfony\Component\DependencyInjection\Attribute; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Contracts\Service\Attribute\SubscribedService; +use Symfony\Contracts\Service\ServiceSubscriberInterface; +/** + * Autowires a service locator based on a tag name or an explicit list of key => service-type pairs. + */ #[\Attribute(\Attribute::TARGET_PARAMETER)] class AutowireLocator extends Autowire { - public function __construct(string ...$serviceIds) - { - $values = []; - - foreach ($serviceIds as $key => $serviceId) { - if ($nullable = str_starts_with($serviceId, '?')) { - $serviceId = substr($serviceId, 1); - } - - if (is_numeric($key)) { - $key = $serviceId; - } - - $values[$key] = new Reference( - $serviceId, - $nullable ? ContainerInterface::IGNORE_ON_INVALID_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, - ); + /** + * @see ServiceSubscriberInterface::getSubscribedServices() + * + * @param string|array $services An explicit list of services or a tag name + * @param string|string[] $exclude A service or a list of services to exclude + */ + public function __construct( + string|array $services, + string $indexAttribute = null, + string $defaultIndexMethod = null, + string $defaultPriorityMethod = null, + string|array $exclude = [], + bool $excludeSelf = true, + ) { + $iterator = (new AutowireIterator($services, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, (array) $exclude, $excludeSelf))->value; + + if ($iterator instanceof TaggedIteratorArgument) { + $iterator = new TaggedIteratorArgument($iterator->getTag(), $iterator->getIndexAttribute(), $iterator->getDefaultIndexMethod(), true, $iterator->getDefaultPriorityMethod(), $iterator->getExclude(), $iterator->excludeSelf()); + } elseif ($iterator instanceof IteratorArgument) { + $iterator = $iterator->getValues(); } - parent::__construct(new ServiceLocatorArgument($values)); + parent::__construct(new ServiceLocatorArgument($iterator)); } } diff --git a/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php b/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php index 77c9af17fa5bd..dce969bd2b9f5 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php @@ -11,10 +11,8 @@ namespace Symfony\Component\DependencyInjection\Attribute; -use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; - #[\Attribute(\Attribute::TARGET_PARAMETER)] -class TaggedIterator extends Autowire +class TaggedIterator extends AutowireIterator { public function __construct( public string $tag, @@ -24,6 +22,6 @@ public function __construct( public string|array $exclude = [], public bool $excludeSelf = true, ) { - parent::__construct(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf)); + parent::__construct($tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, $exclude, $excludeSelf); } } diff --git a/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php b/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php index 98426a01f3668..15fb62d1c0f85 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php @@ -11,11 +11,8 @@ namespace Symfony\Component\DependencyInjection\Attribute; -use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; -use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; - #[\Attribute(\Attribute::TARGET_PARAMETER)] -class TaggedLocator extends Autowire +class TaggedLocator extends AutowireLocator { public function __construct( public string $tag, @@ -25,6 +22,6 @@ public function __construct( public string|array $exclude = [], public bool $excludeSelf = true, ) { - parent::__construct(new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf))); + parent::__construct($tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, $exclude, $excludeSelf); } } diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index bf03f8c703a04..0f38ac86c63ae 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -7,7 +7,7 @@ CHANGELOG * Allow using `#[Target]` with no arguments to state that a parameter must match a named autowiring alias * Deprecate `ContainerAwareInterface` and `ContainerAwareTrait`, use dependency injection instead * Add `defined` env var processor that returns `true` for defined and neither null nor empty env vars - * Add `#[AutowireLocator]` attribute + * Add `#[AutowireLocator]` and `#[AutowireIterator]` attributes 6.3 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php index 089da1e79e0fb..deb1f9de879e9 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php @@ -79,7 +79,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed $attributes = []; if ($type instanceof SubscribedService) { - $key = $type->key; + $key = $type->key ?? $key; $attributes = $type->attributes; $type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s::getSubscribedServices()" returns "%s", a type must be set.', $class, SubscribedService::class))); } @@ -87,7 +87,8 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if (!\is_string($type) || !preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type))); } - if ($optionalBehavior = '?' === $type[0]) { + $optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if ('?' === $type[0]) { $type = substr($type, 1); $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } @@ -120,7 +121,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed $name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name; } - $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name, $attributes); + $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior, $name, $attributes); unset($serviceMap[$key]); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireLocatorTest.php b/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireLocatorTest.php index 50b54ac4a93a9..8d90e85592437 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireLocatorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireLocatorTest.php @@ -15,33 +15,33 @@ use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Attribute\AutowireLocator; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; class AutowireLocatorTest extends TestCase { public function testSimpleLocator() { - $locator = new AutowireLocator('foo', 'bar'); + $locator = new AutowireLocator(['foo', 'bar']); $this->assertEquals( - new ServiceLocatorArgument(['foo' => new Reference('foo'), 'bar' => new Reference('bar')]), + new ServiceLocatorArgument(['foo' => new TypedReference('foo', 'foo'), 'bar' => new TypedReference('bar', 'bar')]), $locator->value, ); } public function testComplexLocator() { - $locator = new AutowireLocator( + $locator = new AutowireLocator([ '?qux', - foo: 'bar', - bar: '?baz', - ); + 'foo' => 'bar', + 'bar' => '?baz', + ]); $this->assertEquals( new ServiceLocatorArgument([ - 'qux' => new Reference('qux', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), - 'foo' => new Reference('bar'), - 'bar' => new Reference('baz', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), + 'qux' => new TypedReference('qux', 'qux', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), + 'foo' => new TypedReference('bar', 'bar', name: 'foo'), + 'bar' => new TypedReference('baz', 'baz', ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'bar'), ]), $locator->value, ); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php index ec65075def77c..e44d67add1d0f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php @@ -17,11 +17,11 @@ final class AutowireLocatorConsumer { public function __construct( - #[AutowireLocator( + #[AutowireLocator([ BarTagClass::class, - with_key: FooTagClass::class, - nullable: '?invalid', - )] + 'with_key' => FooTagClass::class, + 'nullable' => '?invalid', + ])] public readonly ContainerInterface $locator, ) { } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index 584a13f6f5e9c..d67a633ac992e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -676,7 +676,7 @@ class WithTaggedIteratorAndTaggedLocator public function fooAction( #[TaggedIterator('foobar')] iterable $iterator, #[TaggedLocator('foobar')] ServiceLocator $locator, - #[AutowireLocator('bar', 'baz')] ContainerInterface $container, + #[AutowireLocator(['bar', 'baz'])] ContainerInterface $container, ) { } } From a87f2e0c248e5e22c9445ba2f371ab667e49faf8 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Tue, 3 Oct 2023 18:33:24 -0400 Subject: [PATCH 2/2] [DependencyInjection] Add tests for `AutowireLocator`/`AutowireIterator` --- .../Tests/Compiler/IntegrationTest.php | 33 ++++++++++++ .../Fixtures/AutowireIteratorConsumer.php | 30 +++++++++++ .../Fixtures/AutowireLocatorConsumer.php | 3 ++ .../Tests/Fixtures/TaggedIteratorConsumer.php | 4 +- .../Tests/Fixtures/TaggedLocatorConsumer.php | 4 +- ...sterControllerArgumentLocatorsPassTest.php | 50 +++++++++++++++---- .../Component/HttpKernel/composer.json | 1 + 7 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireIteratorConsumer.php diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index be51a2b3a5af0..8c8990856f890 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -15,6 +15,7 @@ use Psr\Container\ContainerInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -32,6 +33,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredInterface2; use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredService1; use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredService2; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AutowireIteratorConsumer; use Symfony\Component\DependencyInjection\Tests\Fixtures\AutowireLocatorConsumer; use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass; @@ -392,6 +394,7 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod() public function testLocatorConfiguredViaAttribute() { $container = new ContainerBuilder(); + $container->setParameter('some.parameter', 'foo'); $container->register(BarTagClass::class) ->setPublic(true) ; @@ -411,6 +414,36 @@ public function testLocatorConfiguredViaAttribute() self::assertSame($container->get(BarTagClass::class), $s->locator->get(BarTagClass::class)); self::assertSame($container->get(FooTagClass::class), $s->locator->get('with_key')); self::assertFalse($s->locator->has('nullable')); + self::assertSame('foo', $s->locator->get('subscribed')); + } + + public function testIteratorConfiguredViaAttribute() + { + $container = new ContainerBuilder(); + $container->setParameter('some.parameter', 'foo'); + $container->register(BarTagClass::class) + ->setPublic(true) + ; + $container->register(FooTagClass::class) + ->setPublic(true) + ; + $container->register(AutowireIteratorConsumer::class) + ->setAutowired(true) + ->setPublic(true) + ; + + $container->compile(); + + /** @var AutowireIteratorConsumer $s */ + $s = $container->get(AutowireIteratorConsumer::class); + + self::assertInstanceOf(RewindableGenerator::class, $s->iterator); + + $values = iterator_to_array($s->iterator); + self::assertCount(3, $values); + self::assertSame($container->get(BarTagClass::class), $values[BarTagClass::class]); + self::assertSame($container->get(FooTagClass::class), $values['with_key']); + self::assertSame('foo', $values['subscribed']); } public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredViaAttribute() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireIteratorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireIteratorConsumer.php new file mode 100644 index 0000000000000..b4fb1c58e1fcb --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireIteratorConsumer.php @@ -0,0 +1,30 @@ + + * + * 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\Autowire; +use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +final class AutowireIteratorConsumer +{ + public function __construct( + #[AutowireIterator([ + BarTagClass::class, + 'with_key' => FooTagClass::class, + 'nullable' => '?invalid', + 'subscribed' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%')), + ])] + public readonly iterable $iterator, + ) { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php index e44d67add1d0f..56e8b693b16bc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php @@ -12,7 +12,9 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireLocator; +use Symfony\Contracts\Service\Attribute\SubscribedService; final class AutowireLocatorConsumer { @@ -21,6 +23,7 @@ public function __construct( BarTagClass::class, 'with_key' => FooTagClass::class, 'nullable' => '?invalid', + 'subscribed' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%')), ])] public readonly ContainerInterface $locator, ) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedIteratorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedIteratorConsumer.php index 1f9c98d8e6b96..fd912bc1e93c6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedIteratorConsumer.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedIteratorConsumer.php @@ -11,12 +11,12 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; -use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; +use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; final class TaggedIteratorConsumer { public function __construct( - #[TaggedIterator('foo_bar', indexAttribute: 'foo')] + #[AutowireIterator('foo_bar', indexAttribute: 'foo')] private iterable $param, ) { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedLocatorConsumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedLocatorConsumer.php index 672389dae8481..f5bd518c9cea4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedLocatorConsumer.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedLocatorConsumer.php @@ -12,12 +12,12 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; +use Symfony\Component\DependencyInjection\Attribute\AutowireLocator; final class TaggedLocatorConsumer { public function __construct( - #[TaggedLocator('foo_bar', indexAttribute: 'foo')] + #[AutowireLocator('foo_bar', indexAttribute: 'foo')] private ContainerInterface $locator, ) { } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index d67a633ac992e..ec45a16aac370 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; +use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; use Symfony\Component\DependencyInjection\Attribute\AutowireLocator; use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; @@ -31,6 +32,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\Tests\Fixtures\Suit; +use Symfony\Contracts\Service\Attribute\SubscribedService; class RegisterControllerArgumentLocatorsPassTest extends TestCase { @@ -499,6 +501,7 @@ public function testAutowireAttribute() public function testTaggedIteratorAndTaggedLocatorAttributes() { $container = new ContainerBuilder(); + $container->setParameter('some.parameter', 'bar'); $resolver = $container->register('argument_resolver.service', \stdClass::class)->addArgument([]); $container->register('bar', \stdClass::class)->addTag('foobar'); @@ -517,25 +520,48 @@ public function testTaggedIteratorAndTaggedLocatorAttributes() /** @var ServiceLocator $locator */ $locator = $container->get($locatorId)->get('foo::fooAction'); - $this->assertCount(3, $locator->getProvidedServices()); + $this->assertCount(7, $locator->getProvidedServices()); - $this->assertTrue($locator->has('iterator')); - $this->assertInstanceOf(RewindableGenerator::class, $argIterator = $locator->get('iterator')); + $this->assertTrue($locator->has('iterator1')); + $this->assertInstanceOf(RewindableGenerator::class, $argIterator = $locator->get('iterator1')); $this->assertCount(2, $argIterator); - $this->assertTrue($locator->has('locator')); - $this->assertInstanceOf(ServiceLocator::class, $argLocator = $locator->get('locator')); + $this->assertTrue($locator->has('iterator2')); + $this->assertInstanceOf(RewindableGenerator::class, $argIterator = $locator->get('iterator2')); + $this->assertCount(2, $argIterator); + + $this->assertTrue($locator->has('locator1')); + $this->assertInstanceOf(ServiceLocator::class, $argLocator = $locator->get('locator1')); + $this->assertCount(2, $argLocator); + $this->assertTrue($argLocator->has('bar')); + $this->assertTrue($argLocator->has('baz')); + + $this->assertSame(iterator_to_array($argIterator), [$argLocator->get('bar'), $argLocator->get('baz')]); + + $this->assertTrue($locator->has('locator2')); + $this->assertInstanceOf(ServiceLocator::class, $argLocator = $locator->get('locator2')); $this->assertCount(2, $argLocator); $this->assertTrue($argLocator->has('bar')); $this->assertTrue($argLocator->has('baz')); $this->assertSame(iterator_to_array($argIterator), [$argLocator->get('bar'), $argLocator->get('baz')]); - $this->assertTrue($locator->has('container')); - $this->assertInstanceOf(ServiceLocator::class, $argLocator = $locator->get('container')); + $this->assertTrue($locator->has('container1')); + $this->assertInstanceOf(ServiceLocator::class, $argLocator = $locator->get('container1')); $this->assertCount(2, $argLocator); $this->assertTrue($argLocator->has('bar')); $this->assertTrue($argLocator->has('baz')); + + $this->assertTrue($locator->has('container2')); + $this->assertInstanceOf(ServiceLocator::class, $argLocator = $locator->get('container2')); + $this->assertCount(1, $argLocator); + $this->assertTrue($argLocator->has('foo')); + $this->assertSame('bar', $argLocator->get('foo')); + + $this->assertTrue($locator->has('iterator3')); + $this->assertInstanceOf(RewindableGenerator::class, $argIterator = $locator->get('iterator3')); + $this->assertCount(1, $argIterator); + $this->assertSame('bar', iterator_to_array($argIterator)['foo']); } } @@ -674,9 +700,13 @@ public function fooAction( class WithTaggedIteratorAndTaggedLocator { public function fooAction( - #[TaggedIterator('foobar')] iterable $iterator, - #[TaggedLocator('foobar')] ServiceLocator $locator, - #[AutowireLocator(['bar', 'baz'])] ContainerInterface $container, + #[TaggedIterator('foobar')] iterable $iterator1, + #[AutowireIterator('foobar')] iterable $iterator2, + #[TaggedLocator('foobar')] ServiceLocator $locator1, + #[AutowireLocator('foobar')] ServiceLocator $locator2, + #[AutowireLocator(['bar', 'baz'])] ContainerInterface $container1, + #[AutowireLocator(['foo' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%'))])] ContainerInterface $container2, + #[AutowireIterator(['foo' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%'))])] iterable $iterator3, ) { } } diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 3a8ecf5cd7cb9..125f4c9ea5d4e 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -63,6 +63,7 @@ "symfony/http-client-contracts": "<2.5", "symfony/mailer": "<5.4", "symfony/messenger": "<5.4", + "symfony/service-contracts": "<3.2", "symfony/translation": "<5.4", "symfony/translation-contracts": "<2.5", "symfony/twig-bridge": "<5.4",