🌐 AI搜索 & 代理 主页
Skip to content

Commit 58e136a

Browse files
committed
[DependencyInjection] Fix lazy proxy creation for interfaces aliased to final classes
1 parent 100e26e commit 58e136a

File tree

3 files changed

+127
-3
lines changed

3 files changed

+127
-3
lines changed

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

Lines changed: 33 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,36 @@ 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+
* @return string The class/interface name to proxy
767+
*/
768+
private function resolveProxyType(string $originalType, string $serviceId): string
769+
{
770+
// Resolve aliases to get the actual service definition
771+
while ($this->container->hasAlias($serviceId)) {
772+
$serviceId = (string) $this->container->getAlias($serviceId);
773+
}
774+
775+
if (!$this->container->hasDefinition($serviceId)) {
776+
return $originalType;
777+
}
778+
779+
$resolvedType = $this->container->findDefinition($serviceId)->getClass();
780+
if (!class_exists($resolvedType, false) && !interface_exists($resolvedType, false)) {
781+
return $originalType;
782+
}
783+
784+
$reflector = $this->container->getReflectionClass($resolvedType, false);
785+
if ($reflector && $reflector->isFinal() && \PHP_VERSION_ID < 80400) {
786+
return $originalType; // Proxy the original type-hint (interface/parent)
787+
}
788+
789+
return $resolvedType; // Proxy the concrete class
790+
}
761791
}

src/Symfony/Component/DependencyInjection/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
}

src/Symfony/Component/DependencyInjection/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)