Skip to content

Commit c75b075

Browse files
kbondnicolas-grekas
authored andcommitted
[DI] Simplify using DI attributes with ServiceLocator/Iterator's
1 parent 8254cc7 commit c75b075

File tree

6 files changed

+51
-114
lines changed

6 files changed

+51
-114
lines changed

Attribute/AutowireIterator.php

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,67 +11,25 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Attribute;
1313

14-
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1514
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
16-
use Symfony\Component\DependencyInjection\ContainerInterface;
17-
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
18-
use Symfony\Component\DependencyInjection\TypedReference;
19-
use Symfony\Contracts\Service\Attribute\SubscribedService;
20-
use Symfony\Contracts\Service\ServiceSubscriberInterface;
2115

2216
/**
23-
* Autowires an iterator of services based on a tag name or an explicit list of key => service-type pairs.
17+
* Autowires an iterator of services based on a tag name.
2418
*/
2519
#[\Attribute(\Attribute::TARGET_PARAMETER)]
2620
class AutowireIterator extends Autowire
2721
{
2822
/**
29-
* @see ServiceSubscriberInterface::getSubscribedServices()
30-
*
31-
* @param string|array<string|SubscribedService> $services A tag name or an explicit list of services
32-
* @param string|string[] $exclude A service or a list of services to exclude
23+
* @param string|string[] $exclude A service or a list of services to exclude
3324
*/
3425
public function __construct(
35-
string|array $services,
26+
string $tag,
3627
string $indexAttribute = null,
3728
string $defaultIndexMethod = null,
3829
string $defaultPriorityMethod = null,
3930
string|array $exclude = [],
4031
bool $excludeSelf = true,
4132
) {
42-
if (\is_string($services)) {
43-
parent::__construct(new TaggedIteratorArgument($services, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf));
44-
45-
return;
46-
}
47-
48-
$references = [];
49-
50-
foreach ($services as $key => $type) {
51-
$attributes = [];
52-
53-
if ($type instanceof SubscribedService) {
54-
$key = $type->key ?? $key;
55-
$attributes = $type->attributes;
56-
$type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s" is used, a type must be set.', SubscribedService::class)));
57-
}
58-
59-
if (!\is_string($type) || !preg_match('/(?(DEFINE)(?<cn>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?<fqcn>(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) {
60-
throw new InvalidArgumentException(sprintf('"%s" is not a PHP type for key "%s".', \is_string($type) ? $type : get_debug_type($type), $key));
61-
}
62-
$optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
63-
if ('?' === $type[0]) {
64-
$type = substr($type, 1);
65-
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
66-
}
67-
if (\is_int($name = $key)) {
68-
$key = $type;
69-
$name = null;
70-
}
71-
72-
$references[$key] = new TypedReference($type, $type, $optionalBehavior, $name, $attributes);
73-
}
74-
75-
parent::__construct(new IteratorArgument($references));
33+
parent::__construct(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf));
7634
}
7735
}

Attribute/AutowireLocator.php

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Attribute;
1313

14-
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1514
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1615
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
16+
use Symfony\Component\DependencyInjection\ContainerInterface;
17+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
18+
use Symfony\Component\DependencyInjection\TypedReference;
1719
use Symfony\Contracts\Service\Attribute\SubscribedService;
1820
use Symfony\Contracts\Service\ServiceSubscriberInterface;
1921

@@ -37,14 +39,44 @@ public function __construct(
3739
string|array $exclude = [],
3840
bool $excludeSelf = true,
3941
) {
40-
$iterator = (new AutowireIterator($services, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, (array) $exclude, $excludeSelf))->value;
42+
if (\is_string($services)) {
43+
parent::__construct(new ServiceLocatorArgument(new TaggedIteratorArgument($services, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf)));
4144

42-
if ($iterator instanceof TaggedIteratorArgument) {
43-
$iterator = new TaggedIteratorArgument($iterator->getTag(), $iterator->getIndexAttribute(), $iterator->getDefaultIndexMethod(), true, $iterator->getDefaultPriorityMethod(), $iterator->getExclude(), $iterator->excludeSelf());
44-
} elseif ($iterator instanceof IteratorArgument) {
45-
$iterator = $iterator->getValues();
45+
return;
4646
}
4747

48-
parent::__construct(new ServiceLocatorArgument($iterator));
48+
$references = [];
49+
50+
foreach ($services as $key => $type) {
51+
$attributes = [];
52+
53+
if ($type instanceof Autowire) {
54+
$references[$key] = $type;
55+
continue;
56+
}
57+
58+
if ($type instanceof SubscribedService) {
59+
$key = $type->key ?? $key;
60+
$attributes = $type->attributes;
61+
$type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s" is used, a type must be set.', SubscribedService::class)));
62+
}
63+
64+
if (!\is_string($type) || !preg_match('/(?(DEFINE)(?<cn>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?<fqcn>(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) {
65+
throw new InvalidArgumentException(sprintf('"%s" is not a PHP type for key "%s".', \is_string($type) ? $type : get_debug_type($type), $key));
66+
}
67+
$optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
68+
if ('?' === $type[0]) {
69+
$type = substr($type, 1);
70+
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
71+
}
72+
if (\is_int($name = $key)) {
73+
$key = $type;
74+
$name = null;
75+
}
76+
77+
$references[$key] = new TypedReference($type, $type, $optionalBehavior, $name, $attributes);
78+
}
79+
80+
parent::__construct(new ServiceLocatorArgument($references));
4981
}
5082
}

Compiler/RegisterServiceSubscribersPass.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Container\ContainerInterface as PsrContainerInterface;
1515
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1616
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
17+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1718
use Symfony\Component\DependencyInjection\ContainerInterface;
1819
use Symfony\Component\DependencyInjection\Definition;
1920
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -78,6 +79,11 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
7879
foreach ($class::getSubscribedServices() as $key => $type) {
7980
$attributes = [];
8081

82+
if (!isset($serviceMap[$key]) && $type instanceof Autowire) {
83+
$subscriberMap[$key] = $type;
84+
continue;
85+
}
86+
8187
if ($type instanceof SubscribedService) {
8288
$key = $type->key ?? $key;
8389
$attributes = $type->attributes;

Tests/Compiler/IntegrationTest.php

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Psr\Container\ContainerInterface;
1616
use Symfony\Component\Config\FileLocator;
1717
use Symfony\Component\DependencyInjection\Alias;
18-
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
1918
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
2019
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
2120
use Symfony\Component\DependencyInjection\ChildDefinition;
@@ -33,7 +32,6 @@
3332
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredInterface2;
3433
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredService1;
3534
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredService2;
36-
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutowireIteratorConsumer;
3735
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutowireLocatorConsumer;
3836
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
3937
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
@@ -415,35 +413,7 @@ public function testLocatorConfiguredViaAttribute()
415413
self::assertSame($container->get(FooTagClass::class), $s->locator->get('with_key'));
416414
self::assertFalse($s->locator->has('nullable'));
417415
self::assertSame('foo', $s->locator->get('subscribed'));
418-
}
419-
420-
public function testIteratorConfiguredViaAttribute()
421-
{
422-
$container = new ContainerBuilder();
423-
$container->setParameter('some.parameter', 'foo');
424-
$container->register(BarTagClass::class)
425-
->setPublic(true)
426-
;
427-
$container->register(FooTagClass::class)
428-
->setPublic(true)
429-
;
430-
$container->register(AutowireIteratorConsumer::class)
431-
->setAutowired(true)
432-
->setPublic(true)
433-
;
434-
435-
$container->compile();
436-
437-
/** @var AutowireIteratorConsumer $s */
438-
$s = $container->get(AutowireIteratorConsumer::class);
439-
440-
self::assertInstanceOf(RewindableGenerator::class, $s->iterator);
441-
442-
$values = iterator_to_array($s->iterator);
443-
self::assertCount(3, $values);
444-
self::assertSame($container->get(BarTagClass::class), $values[BarTagClass::class]);
445-
self::assertSame($container->get(FooTagClass::class), $values['with_key']);
446-
self::assertSame('foo', $values['subscribed']);
416+
self::assertSame('foo', $s->locator->get('subscribed1'));
447417
}
448418

449419
public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredViaAttribute()

Tests/Fixtures/AutowireIteratorConsumer.php

Lines changed: 0 additions & 30 deletions
This file was deleted.

Tests/Fixtures/AutowireLocatorConsumer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public function __construct(
2424
'with_key' => FooTagClass::class,
2525
'nullable' => '?invalid',
2626
'subscribed' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%')),
27+
'subscribed1' => new Autowire('%some.parameter%'),
2728
])]
2829
public readonly ContainerInterface $locator,
2930
) {

0 commit comments

Comments
 (0)