8000 feature #43479 [DependencyInjection] autowire union and intersection … · symfony/symfony@21528c6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 21528c6

Browse files
committed
feature #43479 [DependencyInjection] autowire union and intersection types (nicolas-grekas)
This PR was merged into the 5.4 branch. Discussion ---------- [DependencyInjection] autowire union and intersection types | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | #43325 | License | MIT | Doc PR | - This PR allows autowiring an argument of type `NormalizerInterface&DenormalizerInterface` if both individual types have a corresponding autowiring alias, and if both aliases point to the very same service. This works the same with union types. Commits ------- aba31f9 [DependencyInjection] autowire union and intersection types
2 parents a61110e + aba31f9 commit 21528c6

File tree

4 files changed

+102
-6
lines changed

4 files changed

+102
-6
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add `service_closure()` to the PHP-DSL
88
* Add support for autoconfigurable attributes on methods, properties and parameters
99
* Make auto-aliases private by default
10+
* Add support for autowiring union and intersection types
1011

1112
5.3
1213
---

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

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class AutowirePass extends AbstractRecursivePass
4545
private $decoratedMethodIndex;
4646
private $decoratedMethodArgumentIndex;
4747
private $typesClone;
48+
private $combinedAliases;
4849

4950
public function __construct(bool $throwOnAutowireException = true)
5051
{
@@ -60,6 +61,8 @@ public function __construct(bool $throwOnAutowireException = true)
6061
*/
6162
public function process(ContainerBuilder $container)
6263
{
64+
$this->populateCombinedAliases($container);
65+
6366
try {
6467
$this->typesClone = clone $this;
6568
parent::process($container);
@@ -72,6 +75,7 @@ public function process(ContainerBuilder $container)
7275
$this->decoratedMethodIndex = null;
7376
$this->decoratedMethodArgumentIndex = null;
7477
$this->typesClone = null;
78+
$this->combinedAliases = [];
7579
}
7680
}
7781

@@ -223,8 +227,6 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot,
223227
/**
224228
* Autowires the constructor or a method.
225229
*
226-
* @return array
227-
*
228230
* @throws AutowiringFailedException
229231
*/
230232
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes, int $methodIndex): array
@@ -363,8 +365,12 @@ private function getAutowiredReference(TypedReference $reference): ?TypedReferen
363365
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
364366
}
365367

368+
if (null !== ($alias = $this->combinedAliases[$alias] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) {
369+
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
370+
}
371+
366372
if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) {
367-
foreach ($this->container->getAliases() as $id => $alias) {
373+
foreach ($this->container->getAliases() + $this->combinedAliases as $id => $alias) {
368374
if ($name === (string) $alias && str_starts_with($id, $type.' $')) {
369375
return new TypedReference($name, $type, $reference->getInvalidBehavior());
370376
}
@@ -376,6 +382,10 @@ private function getAutowiredReference(TypedReference $reference): ?TypedReferen
376382
return new TypedReference($type, $type, $reference->getInvalidBehavior());
377383
}
378384

385+
if (null !== ($alias = $this->combinedAliases[$type] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) {
386+
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
387+
}
388+
379389
return null;
380390
}
381391

@@ -565,4 +575,45 @@ private function populateAutowiringAlias(string $id): void
565575
$this->autowiringAliases[$type][$name] = $name;
566576
}
567577
}
578+
579+
private function populateCombinedAliases(ContainerBuilder $container): void
580+
{
581+
$this->combinedAliases = [];
582+
$reverseAliases = [];
583+
584+
foreach ($container->getAliases() as $id => $alias) {
585+
if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) {
586+
continue;
587+
}
588+
589+
$type = $m[2];
590+
$name = $m[3] ?? '';
591+
$reverseAliases[(string) $alias][$name][] = $type;
592+
}
593+
594+
foreach ($reverseAliases as $alias => $names) {
595+
foreach ($names as $name => $types) {
596+
if (2 > $count = \count($types)) {
597+
continue;
598+
}
599+
sort($types);
600+
$i = 1 << $count;
601+
602+
// compute the powerset of the list of types
603+
while ($i--) {
604+
$set = [];
605+
for ($j = 0; $j < $count; ++$j) {
606+
if ($i & (1 << $j)) {
607+
$set[] = $types[$j];
608+
}
609+
}
610+
611+
if (2 <= \count($set)) {
612+
$this->combinedAliases[implode('&', $set).('' === $name ? '' : ' $'.$name)] = $alias;
613+
$this->combinedAliases[implode('|', $set).('' === $name ? '' : ' $'.$name)] = $alias;
614+
}
615+
}
616+
}
617+
}
618+
}
568619
}

src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionPa
7070
}
7171
}
7272

73+
sort($types);
74+
7375
return $types ? implode($glue, $types) : null;
7476
}
7577
}

src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,31 @@ public function testTypeNotGuessableUnionType()
258258
$pass->process($container);
259259
}
260260

261+
/**
262+
* @requires PHP 8
263+
*/
264+
public function testGuessableUnionType()
265+
{
266+
$container = new ContainerBuilder();
267+
268+
$container->register('b', \stcClass::class);
269+
$container->setAlias(CollisionA::class.' $collision', 'b');
270+
$container->setAlias(CollisionB::class.' $collision', 'b');
271+
272+
$aDefinition = $container->register('a', UnionClasses::class);
273+
$aDefinition->setAutowired(true);
274+
275+
$pass = new AutowirePass();
276+
$pass->process($container);
277+
278+
$this->assertSame('b', (string) $aDefinition->getArgument(0));
279+
}
280+
261281
/**
262282
* @requires PHP 8.1
263283
*/
264284
public function testTypeNotGuessableIntersectionType()
265285
{
266-
$this->expectException(AutowiringFailedException::class);
267-
$this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\IntersectionClasses::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface&Symfony\Component\DependencyInjection\Tests\Compiler\AnotherInterface" but this class was not found.');
268286
$container = new ContainerBuilder();
269287

270288
$container->register(CollisionInterface::class);
@@ -273,8 +291,32 @@ public function testTypeNotGuessableIntersectionType()
273291
$aDefinition = $container->register('a', IntersectionClasses::class);
274292
$aDefinition->setAutowired(true);
275293

294+
$pass = new AutowirePass();
295+
296+
$this->expectException(AutowiringFailedException::class);
297+
$this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\IntersectionClasses::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\AnotherInterface&Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but this class was not found.');
298+
$pass->process($container);
299+
}
300+
301+
/**
302+
* @requires PHP 8.1
303+
*/
304+
public function testGuessableIntersectionType()
305+
{
306+
$container = new ContainerBuilder();
307+
308+
$container->register('b', \stcClass::class);
309+
$container->setAlias(CollisionInterface::class, 'b');
310+
$container->setAlias(AnotherInterface::class, 'b');
311+
$container->setAlias(DummyInterface::class, 'b');
312+
313+
$aDefinition = $container->register('a', IntersectionClasses::class);
314+
$aDefinition->setAutowired(true);
315+
276316
$pass = new AutowirePass();
277317
$pass->process($container);
318+
319+
$this->assertSame('b', (string) $aDefinition->getArgument(0));
278320
}
279321

280322
public function testTypeNotGuessableWithTypeSet()
@@ -516,7 +558,7 @@ public function testScalarArgsCannotBeAutowired()
516558
public function testUnionScalarArgsCannotBeAutowired()
517559
{
518560
$this->expectException(AutowiringFailedException::class);
519-
$this->expectExceptionMessage('Cannot autowire service "union_scalars": argument "$timeout" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionScalars::__construct()" is type-hinted "int|float", you should configure its value explicitly.');
561+
$this->expectExceptionMessage('Cannot autowire service "union_scalars": argument "$timeout" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionScalars::__construct()" is type-hinted "float|int", you should configure its value explicitly.');
520562
$container = new ContainerBuilder();
521563

522564
$container->register('union_scalars', UnionScalars::class)

0 commit comments

Comments
 (0)
0