diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 89369b3c32daf..8927cdc99011a 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -335,9 +335,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a $value = $this->doProcessValue($value); } elseif ($lazy = $attribute->lazy) { $value ??= $getValue(); - if ($this->container->has($value->getType())) { - $type = $this->container->findDefinition($value->getType())->getClass(); - } + $type = $this->resolveProxyType($type, $value->getType()); $definition = (new Definition($type)) ->setFactory('current') ->setArguments([[$value]]) @@ -758,4 +756,30 @@ private function getCombinedAlias(string $type, ?string $name = null): ?string return $alias; } + + /** + * Resolves the class name that should be proxied for a lazy service. + * + * @param string $originalType The original parameter type-hint (e.g., the interface) + * @param string $serviceId The service ID the type-hint resolved to (e.g., the alias) + */ + private function resolveProxyType(string $originalType, string $serviceId): string + { + if (!$this->container->has($serviceId)) { + return $originalType; + } + + $resolvedType = $this->container->findDefinition($serviceId)->getClass(); + $resolvedType = $this->container->getParameterBag()->resolveValue($resolvedType); + + if (!$resolvedType || !class_exists($resolvedType, false) && !interface_exists($resolvedType, false)) { + return $originalType; + } + + if (\PHP_VERSION_ID < 80400 && $this->container->getReflectionClass($resolvedType, false)->isFinal()) { + return $originalType; + } + + return $resolvedType; + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index d6bbbc70ffc09..b78e6616f18fa 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -1411,4 +1411,45 @@ public function testAutowireAttributeWithEnvVar() $this->assertSame('%env(bool:ENABLED)%', $container->resolveEnvPlaceholders($definition->getArguments()[0])); $this->assertSame('%env(default::OPTIONAL)%', $container->resolveEnvPlaceholders($definition->getArguments()[1])); } + + public function testLazyProxyForInterfaceWithFinalImplementation() + { + $container = new ContainerBuilder(); + $container->register('final_impl', FinalLazyProxyImplementation::class); + $container->setAlias(LazyProxyTestInterface::class, 'final_impl'); + + $container->register(LazyProxyInterfaceConsumer::class) + ->setAutoconfigured(true) + ->setAutowired(true) + ->setPublic(true); + + $container->compile(); + + $service = $container->get(LazyProxyInterfaceConsumer::class); + $this->assertInstanceOf(LazyProxyInterfaceConsumer::class, $service); + + // Trigger lazy load + $dep = $service->getDep()->getSelf(); + $this->assertInstanceOf(FinalLazyProxyImplementation::class, $dep); + } + + public function testLazyProxyWithClassInheritance() + { + $container = new ContainerBuilder(); + $container->register(BaseLazyProxyClass::class, ExtendedLazyProxyClass::class); + + $container->register(LazyProxyInheritanceConsumer::class) + ->setAutoconfigured(true) + ->setAutowired(true) + ->setPublic(true); + + $container->compile(); + + $service = $container->get(LazyProxyInheritanceConsumer::class); + $this->assertInstanceOf(LazyProxyInheritanceConsumer::class, $service); + + // Trigger lazy load + $dep = $service->getDependency()->getSelf(); + $this->assertInstanceOf(ExtendedLazyProxyClass::class, $dep); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php index 9e07d0283e396..73d641f5466f2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -524,3 +524,56 @@ public static function staticCreateFooWithParam(mixed $someParam): MyInlineServi return new MyInlineService($someParam); } } + +interface LazyProxyTestInterface +{ + public function getSelf(): self; +} + +final class FinalLazyProxyImplementation implements LazyProxyTestInterface +{ + public function getSelf(): self + { + return $this; + } +} + +class BaseLazyProxyClass +{ + public function getSelf(): self + { + return $this; + } +} + +class ExtendedLazyProxyClass extends BaseLazyProxyClass +{ + public function getSelf(): self + { + return $this; + } +} + +class LazyProxyInterfaceConsumer +{ + public function __construct(#[Autowire(lazy: true)] private readonly LazyProxyTestInterface $dep) + { + } + + public function getDep(): LazyProxyTestInterface + { + return $this->dep; + } +} + +class LazyProxyInheritanceConsumer +{ + public function __construct(#[Autowire(lazy: true)] private readonly BaseLazyProxyClass $dep) + { + } + + public function getDependency(): BaseLazyProxyClass + { + return $this->dep; + } +}