8000 [DependencyInjection] Add `#[AutowireIterator]` attribute and improve… · symfony/symfony@8b9073c · GitHub
[go: up one dir, main page]

Skip to content

Commit 8b9073c

Browse files
[DependencyInjection] Add #[AutowireIterator] attribute and improve #[AutowireLocator]
1 parent 7be1c03 commit 8b9073c

File tree

9 files changed

+132
-45
lines changed

9 files changed

+132
-45
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Attribute;
13+
14+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
15+
use Symfony\Component\DependencyInjection\ContainerInterface;
16+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
17+
use Symfony\Component\DependencyInjection\Exception\LogicException;
18+
use Symfony\Component\DependencyInjection\TypedReference;
19+
use Symfony\Contracts\Service\Attribute\SubscribedService;
20+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
21+
22+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
23+
class AutowireIterator extends Autowire
24+
{
25+
/**
26+
* @see ServiceSubscriberInterface::getSubscribedServices()
27+
*
28+
* @param array<string|SubscribedService> $services
29+
*/
30+
public function __construct(
31+
array $services = [],
32+
string $tag = null,
33+
string $indexAttribute = null,
34+
string $defaultIndexMethod = null,
35+
string $defaultPriorityMethod = null,
36+
string|array $exclude = [],
37+
bool $excludeSelf = true,
38+
) {
39+
if (null !== $tag || !$services) {
40+
if ($services && 1 < \func_num_args()) {
41+
throw new LogicException(sprintf('The $services argument of "#[%s]" cannot be used together with $tag-related arguments.', $this::class));
42+
}
43+
44+
parent::__construct(new TaggedIteratorArgument($tag ?? '', $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf));
45+
46+
return;
47+
}
48+
49+
$references = [];
50+
51+
foreach ($services as $key => $type) {
52+
$attributes = [];
53+
54+
if ($type instanceof SubscribedService) {
55+
$key = $type->key;
56+
$attributes = $type->attributes;
57+
$type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s" is used, a type must be set.', SubscribedService::class)));
58+
}
59+
60+
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)) {
61+
throw new InvalidArgumentException(sprintf('"#[%s]" must list valid PHP types, "%s" is invalid for key "%s".', $this::class, \is_string($type) ? $type : get_debug_type($type), $key));
62+
}
63+
if ($optionalBehavior = '?' === $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 ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name, $attributes);
73+
}
74+
75+
parent::__construct($references);
76+
}
77+
}

src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,33 @@
1212
namespace Symfony\Component\DependencyInjection\Attribute;
1313

1414
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
15-
use Symfony\Component\DependencyInjection\ContainerInterface;
16-
use Symfony\Component\DependencyInjection\Reference;
15+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
16+
use Symfony\Contracts\Service\Attribute\SubscribedService;
17+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
1718

1819
#[\Attribute(\Attribute::TARGET_PARAMETER)]
1920
class AutowireLocator extends Autowire
2021
{
21-
public function __construct(string ...$serviceIds)
22-
{
23-
$values = [];
24-
25-
foreach ($serviceIds as $key => $serviceId) {
26-
if ($nullable = str_starts_with($serviceId, '?')) {
27-
$serviceId = substr($serviceId, 1);
28-
}
29-
30-
if (is_numeric($key)) {
31-
$key = $serviceId;
32-
}
33-
34-
$values[$key] = new Reference(
35-
$serviceId,
36-
$nullable ? ContainerInterface::IGNORE_ON_INVALID_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE,
37-
);
22+
/**
23+
* @see ServiceSubscriberInterface::getSubscribedServices()
24+
*
25+
* @param array<string|SubscribedService> $services
26+
*/
27+
public function __construct(
28+
array $services = [],
29+
string $tag = null,
30+
?string $indexAttribute = null,
31+
?string $defaultIndexMethod = null,
32+
?string $defaultPriorityMethod = null,
33+
string|array $exclude = [],
34+
bool $excludeSelf = true,
35+
) {
36+
$iterator = (new AutowireIterator($services, $tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, (array) $exclude, $excludeSelf))->value;
37+
38+
if ($iterator instanceof TaggedIteratorArgument) {
39+
$iterator = new TaggedIteratorArgument($iterator->getTag(), $iterator->getIndexAttribute(), $iterator->getDefaultIndexMethod(), true, $iterator->getDefaultPriorityMethod(), $iterator->getExclude(), $iterator->excludeSelf());
3840
}
3941

40-
parent::__construct(new ServiceLocatorArgument($values));
42+
parent::__construct(new ServiceLocatorArgument($iterator));
4143
}
4244
}

src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Attribute;
1313

14-
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
15-
1614
#[\Attribute(\Attribute::TARGET_PARAMETER)]
17-
class TaggedIterator extends Autowire
15+
class TaggedIterator extends AutowireIterator
1816
{
1917
public function __construct(
2018
public string $tag,
@@ -24,6 +22,6 @@ public function __construct(
2422
public string|array $exclude = [],
2523
public bool $excludeSelf = true,
2624
) {
27-
parent::__construct(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf));
25+
parent::__construct([], $tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, $exclude, $excludeSelf);
2826
}
2927
}

src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Attribute;
1313

14-
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
15-
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
16-
1714
#[\Attribute(\Attribute::TARGET_PARAMETER)]
18-
class TaggedLocator extends Autowire
15+
class TaggedLocator extends AutowireLocator
1916
{
2017
public function __construct(
2118
public string $tag,
@@ -25,6 +22,6 @@ public function __construct(
2522
public string|array $exclude = [],
2623
public bool $excludeSelf = true,
2724
) {
28-
parent::__construct(new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf)));
25+
parent::__construct([], $tag, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, $exclude, $excludeSelf);
2926
}
3027
}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ CHANGELOG
77
* Allow using `#[Target]` with no arguments to state that a parameter must match a named autowiring alias
88
* Deprecate `ContainerAwareInterface` and `ContainerAwareTrait`, use dependency injection instead
99
* Add `defined` env var processor that returns `true` for defined and neither null nor empty env vars
10-
* Add `#[AutowireLocator]` attribute
10+
* Add `#[AutowireLocator]` and `#[AutowireIterator]` attributes
1111

1212
6.3
1313
---

src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\Config\Resource\ClassExistenceResource;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
16+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1517
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1618
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
1719
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
@@ -124,6 +126,17 @@ private function doProcessValue(mixed $value, bool $isRoot = false): mixed
124126
if ($attribute === $v = $this->processValue($attribute)) {
125127
continue;
126128
}
129+
if ($v instanceof ServiceLocatorArgument) {
130+
$v = $v->getTaggedIteratorArgument() ?? $v;
131+
}
132+
if ($v instanceof TaggedIteratorArgument) {
133+
if ('' === $v->getTag()) {
134+
$v = new TaggedIteratorArgument($value->getType(), $v->getIndexAttribute(), $v->getDefaultIndexMethod(), $v->needsIndexes(), $v->getDefaultPriorityMethod(), $v->getExclude(), $v->excludeSelf());
135+
}
136+
if ($attribute->value instanceof ServiceLocatorArgument) {
137+
$v = new ServiceLocatorArgument($v);
138+
}
139+
}
127140
if (!$attribute instanceof Autowire || !$v instanceof Reference) {
128141
return $v;
129142
}

src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireLocatorTest.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,33 @@
1515
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1616
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
1717
use Symfony\Component\DependencyInjection\ContainerInterface;
18-
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\DependencyInjection\TypedReference;
1919

2020
class AutowireLocatorTest extends TestCase
2121
{
2222
public function testSimpleLocator()
2323
{
24-
$locator = new AutowireLocator('foo', 'bar');
24+
$locator = new AutowireLocator(['foo', 'bar']);
2525

2626
$this->assertEquals(
27-
new ServiceLocatorArgument(['foo' => new Reference('foo'), 'bar' => new Reference('bar')]),
27+
new ServiceLocatorArgument(['foo' => new TypedReference('foo', 'foo'), 'bar' => new TypedReference('bar', 'bar')]),
2828
$locator->value,
2929
);
3030
}
3131

3232
public function testComplexLocator()
3333
{
34-
$locator = new AutowireLocator(
34+
$locator = new AutowireLocator([
3535
'?qux',
36-
foo: 'bar',
37-
bar: '?baz',
38-
);
36+
'foo' => 'bar',
37+
'bar' => '?baz',
38+
]);
3939

4040
$this->assertEquals(
4141
new ServiceLocatorArgument([
42-
'qux' => new Reference('qux', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
43-
'foo' => new Reference('bar'),
44-
'bar' => new Reference('baz', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
42+
'qux' => new TypedReference('qux', 'qux', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
43+
'foo' => new TypedReference('bar', 'bar', name: 'foo'),
44+
'bar' => new TypedReference('baz', 'baz', ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'bar'),
4545
]),
4646
$locator->value,
4747
);

src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutowireLocatorConsumer.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
final class AutowireLocatorConsumer
1818
{
1919
public function __construct(
20-
#[AutowireLocator(
20+
#[AutowireLocator([
2121
BarTagClass::class,
22-
with_key: FooTagClass::class,
23-
nullable: '?invalid',
24-
)]
22+
'with_key' => FooTagClass::class,
23+
'nullable' => '?invalid',
24+
])]
2525
public readonly ContainerInterface $locator,
2626
) {
2727
}

src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ class WithTaggedIteratorAndTaggedLocator
676676
public function fooAction(
677677
#[TaggedIterator('foobar')] iterable $iterator,
678678
#[TaggedLocator('foobar')] ServiceLocator $locator,
679-
#[AutowireLocator('bar', 'baz')] ContainerInterface $container,
679+
#[AutowireLocator(['bar', 'baz'])] ContainerInterface $container,
680680
) {
681681
}
682682
}

0 commit comments

Comments
 (0)
0