Skip to content

Commit b8c4fbf

Browse files
committed
Merge branch '6.4' into 7.0
* 6.4: (43 commits) [AssetMapper] Fix entrypoint scripts are not preloaded Fix typo in method resolvePackages Make FormPerformanceTestCase compatible with PHPUnit 10 Avoid calling getInvocationCount() [AssetMapper] Always downloading vendor files [Security] Fix resetting traceable listeners [HttpClient] Fix type error with http_version 1.1 [DependencyInjection] Add tests for `AutowireLocator`/`AutowireIterator` [DependencyInjection] Add `#[AutowireIterator]` attribute and improve `#[AutowireLocator]` Update documentation link Fix typo that causes unit test to fail Fix CS [AssetMapper] Add audit command [Mailer] Use idn encoded address otherwise Brevo throws an error [Messenger] Resend failed retries back to failure transport [FrameworkBundle] Fix call to invalid method in NotificationAssertionsTrait [Validator] Add missing italian translations [Notifier] Fix failing testcase Fix order array sum normalizedData and nestedData Add test for 0 and '0' in PeriodicalTrigger Fix '0' case error and remove duplicate code ...
2 parents 3b288a2 + 8254cc7 commit b8c4fbf

12 files changed

+198
-51
lines changed

Attribute/AutowireIterator.php

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 <[email protected]>
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+
}

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
}

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
}

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
}

CHANGELOG.md

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

2626
6.3
2727
---

Compiler/RegisterServiceSubscribersPass.php

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

7777
if ($type instanceof SubscribedService) {
78-
$key = $type->key;
78+
$key = $type->key ?? $key;
7979
$attributes = $type->attributes;
8080
$type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s::getSubscribedServices()" returns "%s", a type must be set.', $class, SubscribedService::class)));
8181
}
8282

8383
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)) {
8484
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)));
8585
}
86-
if ($optionalBehavior = '?' === $type[0]) {
86+
$optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
87+
if ('?' === $type[0]) {
8788
$type = substr($type, 1);
8889
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
8990
}
@@ -111,7 +112,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
111112
$name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name;
112113
}
113114

114-
$subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name, $attributes);
115+
$subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior, $name, $attributes);
115116
unset($serviceMap[$key]);
116117
}
117118

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
);

Tests/Compiler/IntegrationTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Psr\Container\ContainerInterface;
1616
use Symfony\Component\Config\FileLocator;
1717
use Symfony\Component\DependencyInjection\Alias;
18+
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
1819
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1920
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
2021
use Symfony\Component\DependencyInjection\ChildDefinition;
@@ -32,6 +33,7 @@
3233
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredInterface2;
3334
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredService1;
3435
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredService2;
36+
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutowireIteratorConsumer;
3537
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutowireLocatorConsumer;
3638
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
3739
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
@@ -392,6 +394,7 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
392394
public function testLocatorConfiguredViaAttribute()
393395
{
394396
$container = new ContainerBuilder();
397+
$container->setParameter('some.parameter', 'foo');
395398
$container->register(BarTagClass::class)
396399
->setPublic(true)
397400
;
@@ -411,6 +414,36 @@ public function testLocatorConfiguredViaAttribute()
411414
self::assertSame($container->get(BarTagClass::class), $s->locator->get(BarTagClass::class));
412415
self::assertSame($container->get(FooTagClass::class), $s->locator->get('with_key'));
413416
self::assertFalse($s->locator->has('nullable'));
417+
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']);
414447
}
415448

416449
public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredViaAttribute()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Fixtures;
13+
14+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
15+
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
16+
use Symfony\Contracts\Service\Attribute\SubscribedService;
17+
18+
final class AutowireIteratorConsumer
19+
{
20+
public function __construct(
21+
#[AutowireIterator([
22+
BarTagClass::class,
23+
'with_key' => FooTagClass::class,
24+
'nullable' => '?invalid',
25+
'subscribed' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%')),
26+
])]
27+
public readonly iterable $iterator,
28+
) {
29+
}
30+
}

Tests/Fixtures/AutowireLocatorConsumer.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,19 @@
1212
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
1313

1414
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1516
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
17+
use Symfony\Contracts\Service\Attribute\SubscribedService;
1618

1719
final class AutowireLocatorConsumer
1820
{
1921
public function __construct(
20-
#[AutowireLocator(
22+
#[AutowireLocator([
2123
BarTagClass::class,
22-
with_key: FooTagClass::class,
23-
nullable: '?invalid',
24-
)]
24+
'with_key' => FooTagClass::class,
25+
'nullable' => '?invalid',
26+
'subscribed' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%')),
27+
])]
2528
public readonly ContainerInterface $locator,
2629
) {
2730
}

0 commit comments

Comments
 (0)