Skip to content

[DependencyInjection] Fix cloned lazy services not sharing their dependencies when dumped with PhpDumper #59723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ private function isInlineableDefinition(string $id, Definition $definition): boo
return false;
}

return $this->container->getDefinition($srcId)->isShared();
$srcDefinition = $this->container->getDefinition($srcId);

return $srcDefinition->isShared() && !$srcDefinition->isLazy();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2185,6 +2185,12 @@ private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool
if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) {
return false;
}

// When the source node is a proxy or ghost, it will construct its references only when the node itself is initialized.
// Since the node can be cloned before being fully initialized, we do not know how often its references are used.
if ($this->getProxyDumper()->isProxyCandidate($value)) {
return false;
}
$ids[$edge->getSourceNode()->getId()] = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
use Symfony\Component\DependencyInjection\Tests\Compiler\Wither;
use Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
use Symfony\Component\DependencyInjection\Tests\Fixtures\DependencyContainer;
use Symfony\Component\DependencyInjection\Tests\Fixtures\DependencyContainerInterface;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooWithAbstractArgument;
Expand Down Expand Up @@ -1677,6 +1679,59 @@ public function testWitherWithStaticReturnType()
$this->assertInstanceOf(Foo::class, $wither->foo);
}

public function testCloningLazyGhostWithDependency()
{
$container = new ContainerBuilder();
$container->register('dependency', \stdClass::class);
$container->register(DependencyContainer::class)
->addArgument(new Reference('dependency'))
->setLazy(true)
->setPublic(true);

$container->compile();
$dumper = new PhpDumper($container);
$dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_CloningLazyGhostWithDependency']);
eval('?>'.$dump);

$container = new \Symfony_DI_PhpDumper_Service_CloningLazyGhostWithDependency();

$bar = $container->get(DependencyContainer::class);
$this->assertInstanceOf(DependencyContainer::class, $bar);

$first_clone = clone $bar;
$second_clone = clone $bar;

$this->assertSame($first_clone->dependency, $second_clone->dependency);
}

public function testCloningProxyWithDependency()
{
$container = new ContainerBuilder();
$container->register('dependency', \stdClass::class);
$container->register(DependencyContainer::class)
->addArgument(new Reference('dependency'))
->setLazy(true)
->addTag('proxy', [
'interface' => DependencyContainerInterface::class,
])
->setPublic(true);

$container->compile();
$dumper = new PhpDumper($container);
$dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_CloningProxyWithDependency']);
eval('?>'.$dump);

$container = new \Symfony_DI_PhpDumper_Service_CloningProxyWithDependency();

$bar = $container->get(DependencyContainer::class);
$this->assertInstanceOf(DependencyContainerInterface::class, $bar);

$first_clone = clone $bar;
$second_clone = clone $bar;

$this->assertSame($first_clone->getDependency(), $second_clone->getDependency());
}

public function testCurrentFactoryInlining()
{
$container = new ContainerBuilder();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Tests\Fixtures;

class DependencyContainer implements DependencyContainerInterface
{
public function __construct(
public mixed $dependency,
) {
}

public function getDependency(): mixed
{
return $this->dependency;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Tests\Fixtures;

interface DependencyContainerInterface
{
public function getDependency(): mixed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ services:
- container.decorator: { id: bar, inner: b }
file: file.php
lazy: true
arguments: [!service { class: Class1 }]
arguments: ['@b']
b:
class: Class1
bar:
alias: foo
public: true
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ services:
class: stdClass
public: true
lazy: true
arguments: [[!service { class: stdClass }, do]]
arguments: [['@bar', do]]
factory: [Closure, fromCallable]
bar:
class: stdClass
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ protected function createProxy($class, \Closure $factory)
*/
protected static function getClosureProxyService($container, $lazyLoad = true)
{
return $container->services['closure_proxy'] = new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } };
return $container->services['closure_proxy'] = new class(fn () => ($container->privates['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ protected function createProxy($class, \Closure $factory)
*/
protected static function getClosure1Service($container, $lazyLoad = true)
{
return $container->services['closure1'] = (new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function cloneFoo(?\stdClass $bar = null): \Symfony\Component\DependencyInjection\Tests\Compiler\Foo { return $this->service->cloneFoo(...\func_get_args()); } })->cloneFoo(...);
return $container->services['closure1'] = (new class(fn () => ($container->privates['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function cloneFoo(?\stdClass $bar = null): \Symfony\Component\DependencyInjection\Tests\Compiler\Foo { return $this->service->cloneFoo(...\func_get_args()); } })->cloneFoo(...);
}

/**
Expand All @@ -67,6 +67,6 @@ protected static function getClosure1Service($container, $lazyLoad = true)
*/
protected static function getClosure2Service($container, $lazyLoad = true)
{
return $container->services['closure2'] = (new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name): void { $this->service->__invoke(...\func_get_args()); } })->__invoke(...);
return $container->services['closure2'] = (new class(fn () => ($container->privates['foo_void'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name): void { $this->service->__invoke(...\func_get_args()); } })->__invoke(...);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -373,15 +373,13 @@ protected static function getManager2Service($container)
*/
protected static function getManager3Service($container, $lazyLoad = true)
{
$a = ($container->services['listener3'] ?? self::getListener3Service($container));
$a = ($container->privates['connection3'] ?? self::getConnection3Service($container));

if (isset($container->services['manager3'])) {
return $container->services['manager3'];
}
$b = new \stdClass();
$b->listener = [$a];

return $container->services['manager3'] = new \stdClass($b);
return $container->services['manager3'] = new \stdClass($a);
}

/**
Expand Down Expand Up @@ -481,6 +479,34 @@ protected static function getBar6Service($container)
return $container->privates['bar6'] = new \stdClass($a);
}

/**
* Gets the private 'connection3' shared service.
*
* @return \stdClass
*/
protected static function getConnection3Service($container)
{
$container->privates['connection3'] = $instance = new \stdClass();

$instance->listener = [($container->services['listener3'] ?? self::getListener3Service($container))];

return $instance;
}

/**
* Gets the private 'connection4' shared service.
*
* @return \stdClass
*/
protected static function getConnection4Service($container)
{
$container->privates['connection4'] = $instance = new \stdClass();

$instance->listener = [($container->services['listener4'] ?? self::getListener4Service($container))];

return $instance;
}

/**
* Gets the private 'doctrine.listener' shared service.
*
Expand Down Expand Up @@ -572,13 +598,13 @@ protected static function getMailerInline_TransportFactory_AmazonService($contai
*/
protected static function getManager4Service($container, $lazyLoad = true)
{
$a = new \stdClass();
$a = ($container->privates['connection4'] ?? self::getConnection4Service($container));

$container->privates['manager4'] = $instance = new \stdClass($a);

$a->listener = [($container->services['listener4'] ?? self::getListener4Service($container))];
if (isset($container->privates['manager4'])) {
return $container->privates['manager4'];
}

return $instance;
return $container->privates['manager4'] = new \stdClass($a);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ protected static function getDispatcher2Service($container, $lazyLoad = true)
{
$container->services['dispatcher2'] = $instance = new \stdClass();

$instance->subscriber2 = new \stdClass(($container->services['manager2'] ?? self::getManager2Service($container)));
$instance->subscriber2 = ($container->privates['subscriber2'] ?? self::getSubscriber2Service($container));

return $instance;
}
Expand Down Expand Up @@ -820,4 +820,20 @@ protected static function getManager4Service($container, $lazyLoad = true)

return $container->privates['manager4'] = new \stdClass($a);
}

/**
* Gets the private 'subscriber2' shared service.
*
* @return \stdClass
*/
protected static function getSubscriber2Service($container)
{
$a = ($container->services['manager2'] ?? self::getManager2Service($container));

if (isset($container->privates['subscriber2'])) {
return $container->privates['subscriber2'];
}

return $container->privates['subscriber2'] = new \stdClass($a);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ protected static function getWitherService($container, $lazyLoad = true)

$instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither();

$a = new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo();
$a = ($container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo());

$instance = $instance->withFoo1($a);
$instance = $instance->withFoo2($a);
Expand Down
Loading