Skip to content

Commit 78018de

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

File tree

9 files changed

+129
-48
lines changed

9 files changed

+129
-48
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\IteratorArgument;
15+
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;
21+
22+
/**
23+
* Autowires an iterator of services based on a tag name or an explicit list of key => service-type pairs.
24+
*/
25+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
26+
class AutowireIterator extends Autowire
27+
{
28+
/**
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
33+
*/
34+
public function __construct(
35+
string|array $services,
36+
string $indexAttribute = null,
37+
string $defaultIndexMethod = null,
38+
string $defaultPriorityMethod = null,
39+
string|array $exclude = [],
40+
bool $excludeSelf = true,
41+
) {
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));
76+
}
77+
}

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

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,40 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Attribute;
1313

14+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1415
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
15-
use Symfony\Component\DependencyInjection\ContainerInterface;
16-
use Symfony\Component\DependencyInjection\Reference;
16+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
17+
use Symfony\Contracts\Service\Attribute\SubscribedService;
18+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
1719

20+
/**
21+
* Autowires a service locator based on a tag name or an explicit list of key => service-type pairs.
22+
*/
1823
#[\Attribute(\Attribute::TARGET_PARAMETER)]
1924
class AutowireLocator extends Autowire
2025
{
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-
);
26+
/**
27+
* @see ServiceSubscriberInterface::getSubscribedServices()
28+
*
29+
* @param string|array<string|SubscribedService> $services An explicit list of services or a tag name
30+
* @param string|string[] $exclude A service or a list of services to exclude
31+
*/
32+
public function __construct(
33+
string|array $services,
34+
string $indexAttribute = null,
35+
string $defaultIndexMethod = null,
36+
string $defaultPriorityMethod = null,
37+
string|array $exclude = [],
38+
bool $excludeSelf = true,
39+
) {
40+
$iterator = (new AutowireIterator($services, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, (array) $exclude, $excludeSelf))->value;
41+
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();
3846
}
3947

40-
parent::__construct(new ServiceLocatorArgument($values));
48+
parent::__construct(new ServiceLocatorArgument($iterator));
4149
}
4250
}

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/RegisterServiceSubscribersPass.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,16 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
7979
$attributes = [];
8080

8181
if ($type instanceof SubscribedService) {
82-
$key = $type->key;
82+
$key = $type->key ?? $key;
8383
$attributes = $type->attributes;
8484
$type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s::getSubscribedServices()" returns "%s", a type must be set.', $class, SubscribedService::class)));
8585
}
8686

8787
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)) {
8888
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)));
8989
}
90-
if ($optionalBehavior = '?' === $type[0]) {
90+
$optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
91+
if ('?' === $type[0]) {
9192
$type = substr($type, 1);
9293
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
9394
}
@@ -120,7 +121,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
120121
$name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name;
121122
}
122123

123-
$subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name, $attributes);
124+
$subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior, $name, $attributes);
124125
unset($serviceMap[$key]);
125126
}
126127

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy