Skip to content

Commit 638bb2a

Browse files
bug #62304 [DependencyInjection] Fix lazy proxy creation for interfaces aliased to final classes (yoeunes)
This PR was merged into the 7.3 branch. Discussion ---------- [DependencyInjection] Fix lazy proxy creation for interfaces aliased to final classes | Q | A |---|--- | Branch? | 7.3 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Issues | Fixes #62291 | License | MIT This PR fixes a bug where autowiring a lazy dependency for an interface (e.g., `#[Autowire(lazy: true)] TestInterface`) would fail if that interface was aliased to a `final` class. The `AutowirePass` was attempting to resolve the alias to the concrete `final` class, which causes a fatal error on PHP < 8.4 as `final` classes cannot be proxied. This fix updates the logic to: 1. Always resolve aliases to concrete classes when possible (for non-final classes). 2. **Only** on PHP < 8.4, if the resolved class is `final`, fall back to proxying the original type-hint (the interface) to avoid the fatal error. Tests have been updated to cover these scenarios and now pass on all PHP versions without restrictions. Commits ------- 3dcc31b8679 [DependencyInjection] Fix lazy proxy creation for interfaces aliased to final classes
2 parents cafa38d + d773b94 commit 638bb2a

File tree

3 files changed

+121
-3
lines changed

3 files changed

+121
-3
lines changed

Compiler/AutowirePass.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
335335
$value = $this->doProcessValue($value);
336336
} elseif ($lazy = $attribute->lazy) {
337337
$value ??= $getValue();
338-
if ($this->container->has($value->getType())) {
339-
$type = $this->container->findDefinition($value->getType())->getClass();
340-
}
338+
$type = $this->resolveProxyType($type, $value->getType());
341339
$definition = (new Definition($type))
342340
->setFactory('current')
343341
->setArguments([[$value]])
@@ -758,4 +756,30 @@ private function getCombinedAlias(string $type, ?string $name = null): ?string
758756

759757
return $alias;
760758
}
759+
760+
/**
761+
* Resolves the class name that should be proxied for a lazy service.
762+
*
763+
* @param string $originalType The original parameter type-hint (e.g., the interface)
764+
* @param string $serviceId The service ID the type-hint resolved to (e.g., the alias)
765+
*/
766+
private function resolveProxyType(string $originalType, string $serviceId): string
767+
{
768+
if (!$this->container->has($serviceId)) {
769+
return $originalType;
770+
}
771+
772+
$resolvedType = $this->container->findDefinition($serviceId)->getClass();
773+
$resolvedType = $this->container->getParameterBag()->resolveValue($resolvedType);
774+
775+
if (!$resolvedType || !class_exists($resolvedType, false) && !interface_exists($resolvedType, false)) {
776+
return $originalType;
777+
}
778+
779+
if (\PHP_VERSION_ID < 80400 && $this->container->getReflectionClass($resolvedType, false)->isFinal()) {
780+
return $originalType;
781+
}
782+
783+
return $resolvedType;
784+
}
761785
}

Tests/Compiler/AutowirePassTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,4 +1411,45 @@ public function testAutowireAttributeWithEnvVar()
14111411
$this->assertSame('%env(bool:ENABLED)%', $container->resolveEnvPlaceholders($definition->getArguments()[0]));
14121412
$this->assertSame('%env(default::OPTIONAL)%', $container->resolveEnvPlaceholders($definition->getArguments()[1]));
14131413
}
1414+
1415+
public function testLazyProxyForInterfaceWithFinalImplementation()
1416+
{
1417+
$container = new ContainerBuilder();
1418+
$container->register('final_impl', FinalLazyProxyImplementation::class);
1419+
$container->setAlias(LazyProxyTestInterface::class, 'final_impl');
1420+
1421+
$container->register(LazyProxyInterfaceConsumer::class)
1422+
->setAutoconfigured(true)
1423+
->setAutowired(true)
1424+
->setPublic(true);
1425+
1426+
$container->compile();
1427+
1428+
$service = $container->get(LazyProxyInterfaceConsumer::class);
1429+
$this->assertInstanceOf(LazyProxyInterfaceConsumer::class, $service);
1430+
1431+
// Trigger lazy load
1432+
$dep = $service->getDep()->getSelf();
1433+
$this->assertInstanceOf(FinalLazyProxyImplementation::class, $dep);
1434+
}
1435+
1436+
public function testLazyProxyWithClassInheritance()
1437+
{
1438+
$container = new ContainerBuilder();
1439+
$container->register(BaseLazyProxyClass::class, ExtendedLazyProxyClass::class);
1440+
1441+
$container->register(LazyProxyInheritanceConsumer::class)
1442+
->setAutoconfigured(true)
1443+
->setAutowired(true)
1444+
->setPublic(true);
1445+
1446+
$container->compile();
1447+
1448+
$service = $container->get(LazyProxyInheritanceConsumer::class);
1449+
$this->assertInstanceOf(LazyProxyInheritanceConsumer::class, $service);
1450+
1451+
// Trigger lazy load
1452+
$dep = $service->getDependency()->getSelf();
1453+
$this->assertInstanceOf(ExtendedLazyProxyClass::class, $dep);
1454+
}
14141455
}

Tests/Fixtures/includes/autowiring_classes.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,56 @@ public static function staticCreateFooWithParam(mixed $someParam): MyInlineServi
524524
return new MyInlineService($someParam);
525525
}
526526
}
527+
528+
interface LazyProxyTestInterface
529+
{
530+
public function getSelf(): self;
531+
}
532+
533+
final class FinalLazyProxyImplementation implements LazyProxyTestInterface
534+
{
535+
public function getSelf(): self
536+
{
537+
return $this;
538+
}
539+
}
540+
541+
class BaseLazyProxyClass
542+
{
543+
public function getSelf(): self
544+
{
545+
return $this;
546+
}
547+
}
548+
549+
class ExtendedLazyProxyClass extends BaseLazyProxyClass
550+
{
551+
public function getSelf(): self
552+
{
553+
return $this;
554+
}
555+
}
556+
557+
class LazyProxyInterfaceConsumer
558+
{
559+
public function __construct(#[Autowire(lazy: true)] private readonly LazyProxyTestInterface $dep)
560+
{
561+
}
562+
563+
public function getDep(): LazyProxyTestInterface
564+
{
565+
return $this->dep;
566+
}
567+
}
568+
569+
class LazyProxyInheritanceConsumer
570+
{
571+
public function __construct(#[Autowire(lazy: true)] private readonly BaseLazyProxyClass $dep)
572+
{
573+
}
574+
575+
public function getDependency(): BaseLazyProxyClass
576+
{
577+
return $this->dep;
578+
}
579+
}

0 commit comments

Comments
 (0)