Skip to content

Commit 64afde5

Browse files
committed
bug #46973 [DependencyInjection] Fail gracefully when attempting to autowire composite types (derrabus)
This PR was merged into the 4.4 branch. Discussion ---------- [DependencyInjection] Fail gracefully when attempting to autowire composite types | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Part of #44282 | License | MIT | Doc PR | N/A Symfony 4.4 does not support autowiring union types. Unfortunately, we run into a fatal error when autowiring is attempted for a parameter with an intersection type nested into a union (`(A&B)|C`). Ironically, the error occurs while we try to generate a nice exception message. This PR fixes this, so the developer gets a nice exception message about the parameter that cannot be autowired. For nullable unions however, `null` is injected. This already was the case for `A|null` and it works out of the box for `(A&B)|null`. I've added a test case that covers this case. Commits ------- 2543091 Fail gracefully when attempting to autowire composite types
2 parents 52a2eab + 2543091 commit 64afde5

File tree

4 files changed

+81
-3
lines changed

4 files changed

+81
-3
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionPa
3232
return null;
3333
}
3434

35+
return self::getTypeHintForType($type, $r, $noBuiltin);
36+
}
37+
38+
private static function getTypeHintForType(\ReflectionType $type, \ReflectionFunctionAbstract $r, bool $noBuiltin): ?string
39+
{
3540
$types = [];
3641
$glue = '|';
3742
if ($type instanceof \ReflectionUnionType) {
@@ -46,6 +51,17 @@ public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionPa
4651
}
4752

4853
foreach ($reflectionTypes as $type) {
54+
if ($type instanceof \ReflectionIntersectionType) {
55+
$typeHint = self::getTypeHintForType($type, $r, $noBuiltin);
56+
if (null === $typeHint) {
57+
return null;
58+
}
59+
60+
$types[] = sprintf('(%s)', $typeHint);
61+
62+
continue;
63+
}
64+
4965
if ($type->isBuiltin()) {
5066
if (!$noBuiltin) {
5167
$types[] = $type->getName();

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

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,6 @@ public function testTypeNotGuessableNoServicesFound()
241241
*/
242242
public function testTypeNotGuessableUnionType()
243243
{
244-
$this->expectException(AutowiringFailedException::class);
245-
$this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionClasses::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionA|Symfony\Component\DependencyInjection\Tests\Compiler\CollisionB" but this class was not found.');
246244
$container = new ContainerBuilder();
247245

248246
$container->register(CollisionA::class);
@@ -252,6 +250,9 @@ public function testTypeNotGuessableUnionType()
252250
$aDefinition->setAutowired(true);
253251

254252
$pass = new AutowirePass();
253+
254+
$this->expectException(AutowiringFailedException::class);
255+
$this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionClasses::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionA|Symfony\Component\DependencyInjection\Tests\Compiler\CollisionB" but this class was not found.');
255256
$pass->process($container);
256257
}
257258

@@ -260,17 +261,38 @@ public function testTypeNotGuessableUnionType()
260261
*/
261262
public function testTypeNotGuessableIntersectionType()
262263
{
264+
$container = new ContainerBuilder();
265+
266+
$container->register(CollisionInterface::class);
267+
$container->register(AnotherInterface::class);
268+
269+
$aDefinition = $container->register('a', IntersectionClasses::class);
270+
$aDefinition->setAutowired(true);
271+
272+
$pass = new AutowirePass();
273+
263274
$this->expectException(AutowiringFailedException::class);
264275
$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.');
276+
$pass->process($container);
277+
}
278+
279+
/**
280+
* @requires PHP 8.2
281+
*/
282+
public function testTypeNotGuessableCompositeType()
283+
{
265284
$container = new ContainerBuilder();
266285

267286
$container->register(CollisionInterface::class);
268287
$container->register(AnotherInterface::class);
269288

270-
$aDefinition = $container->register('a', IntersectionClasses::class);
289+
$aDefinition = $container->register('a', CompositeTypeClasses::class);
271290
$aDefinition->setAutowired(true);
272291

273292
$pass = new AutowirePass();
293+
294+
$this->expectException(AutowiringFailedException::class);
295+
$this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\CompositeTypeClasses::__construct()" has type "(Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface&Symfony\Component\DependencyInjection\Tests\Compiler\AnotherInterface)|Symfony\Component\DependencyInjection\Tests\Compiler\YetAnotherInterface" but this class was not found.');
274296
$pass->process($container);
275297
}
276298

@@ -373,6 +395,22 @@ public function testParameterWithNullUnionIsSkipped()
373395
$this->assertNull($definition->getArgument(0));
374396
}
375397

398+
/**
399+
* @requires PHP 8.2
400+
*/
401+
public function testParameterWithNullableIntersectionIsSkipped()
402+
{
403+
$container = new ContainerBuilder();
404+
405+
$optDefinition = $container->register('opt', NullableIntersection::class);
406+
$optDefinition->setAutowired(true);
407+
408+
(new AutowirePass())->process($container);
409+
410+
$definition = $container->getDefinition('opt');
411+
$this->assertNull($definition->getArgument(0));
412+
}
413+
376414
/**
377415
* @requires PHP 8
378416
*/

src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
if (\PHP_VERSION_ID >= 80100) {
1111
require __DIR__.'/intersectiontype_classes.php';
1212
}
13+
if (\PHP_VERSION_ID >= 80200) {
14+
require __DIR__.'/compositetype_classes.php';
15+
}
1316

1417
class Foo
1518
{
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
4+
5+
interface YetAnotherInterface
6+
{
7+
}
8+
9+
class CompositeTypeClasses
10+
{
11+
public function __construct((CollisionInterface&AnotherInterface)|YetAnotherInterface $collision)
12+
{
13+
}
14+
}
15+
16+
class NullableIntersection
17+
{
18+
public function __construct((CollisionInterface&AnotherInterface)|null $a)
19+
{
20+
}
21+
}

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