Skip to content

Commit 3f58cc9

Browse files
pvandommelennicolas-grekas
authored andcommitted
[DependencyInjection] Fix cloned lazy services not sharing their dependencies when dumped with PhpDumper
1 parent 6094d2c commit 3f58cc9

12 files changed

+168
-17
lines changed

Compiler/InlineServiceDefinitionsPass.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ private function isInlineableDefinition(string $id, Definition $definition): boo
224224
return false;
225225
}
226226

227-
return $this->container->getDefinition($srcId)->isShared();
227+
$srcDefinition = $this->container->getDefinition($srcId);
228+
229+
return $srcDefinition->isShared() && !$srcDefinition->isLazy();
228230
}
229231
}

Dumper/PhpDumper.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,6 +2185,12 @@ private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool
21852185
if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) {
21862186
return false;
21872187
}
2188+
2189+
// When the source node is a proxy or ghost, it will construct its references only when the node itself is initialized.
2190+
// Since the node can be cloned before being fully initialized, we do not know how often its references are used.
2191+
if ($this->getProxyDumper()->isProxyCandidate($value)) {
2192+
return false;
2193+
}
21882194
$ids[$edge->getSourceNode()->getId()] = true;
21892195
}
21902196

Tests/Dumper/PhpDumperTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
use Symfony\Component\DependencyInjection\Tests\Compiler\Wither;
5656
use Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation;
5757
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
58+
use Symfony\Component\DependencyInjection\Tests\Fixtures\DependencyContainer;
59+
use Symfony\Component\DependencyInjection\Tests\Fixtures\DependencyContainerInterface;
5860
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute;
5961
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum;
6062
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooWithAbstractArgument;
@@ -1677,6 +1679,59 @@ public function testWitherWithStaticReturnType()
16771679
$this->assertInstanceOf(Foo::class, $wither->foo);
16781680
}
16791681

1682+
public function testCloningLazyGhostWithDependency()
1683+
{
1684+
$container = new ContainerBuilder();
1685+
$container->register('dependency', \stdClass::class);
1686+
$container->register(DependencyContainer::class)
1687+
->addArgument(new Reference('dependency'))
1688+
->setLazy(true)
1689+
->setPublic(true);
1690+
1691+
$container->compile();
1692+
$dumper = new PhpDumper($container);
1693+
$dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_CloningLazyGhostWithDependency']);
1694+
eval('?>'.$dump);
1695+
1696+
$container = new \Symfony_DI_PhpDumper_Service_CloningLazyGhostWithDependency();
1697+
1698+
$bar = $container->get(DependencyContainer::class);
1699+
$this->assertInstanceOf(DependencyContainer::class, $bar);
1700+
1701+
$first_clone = clone $bar;
1702+
$second_clone = clone $bar;
1703+
1704+
$this->assertSame($first_clone->dependency, $second_clone->dependency);
1705+
}
1706+
1707+
public function testCloningProxyWithDependency()
1708+
{
1709+
$container = new ContainerBuilder();
1710+
$container->register('dependency', \stdClass::class);
1711+
$container->register(DependencyContainer::class)
1712+
->addArgument(new Reference('dependency'))
1713+
->setLazy(true)
1714+
->addTag('proxy', [
1715+
'interface' => DependencyContainerInterface::class,
1716+
])
1717+
->setPublic(true);
1718+
1719+
$container->compile();
1720+
$dumper = new PhpDumper($container);
1721+
$dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_CloningProxyWithDependency']);
1722+
eval('?>'.$dump);
1723+
1724+
$container = new \Symfony_DI_PhpDumper_Service_CloningProxyWithDependency();
1725+
1726+
$bar = $container->get(DependencyContainer::class);
1727+
$this->assertInstanceOf(DependencyContainerInterface::class, $bar);
1728+
1729+
$first_clone = clone $bar;
1730+
$second_clone = clone $bar;
1731+
1732+
$this->assertSame($first_clone->getDependency(), $second_clone->getDependency());
1733+
}
1734+
16801735
public function testCurrentFactoryInlining()
16811736
{
16821737
$container = new ContainerBuilder();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
13+
14+
class DependencyContainer implements DependencyContainerInterface
15+
{
16+
public function __construct(
17+
public mixed $dependency,
18+
) {
19+
}
20+
21+
public function getDependency(): mixed
22+
{
23+
return $this->dependency;
24+
}
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
13+
14+
interface DependencyContainerInterface
15+
{
16+
public function getDependency(): mixed;
17+
}

Tests/Fixtures/config/child.expected.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ services:
1111
- container.decorator: { id: bar, inner: b }
1212
file: file.php
1313
lazy: true
14-
arguments: [!service { class: Class1 }]
14+
arguments: ['@b']
15+
b:
16+
class: Class1
1517
bar:
1618
alias: foo
1719
public: true

Tests/Fixtures/config/from_callable.expected.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ services:
88
class: stdClass
99
public: true
1010
lazy: true
11-
arguments: [[!service { class: stdClass }, do]]
11+
arguments: [['@bar', do]]
1212
factory: [Closure, fromCallable]
13+
bar:
14+
class: stdClass

Tests/Fixtures/php/closure_proxy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ protected function createProxy($class, \Closure $factory)
5555
*/
5656
protected static function getClosureProxyService($container, $lazyLoad = true)
5757
{
58-
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()); } };
58+
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()); } };
5959
}
6060
}

Tests/Fixtures/php/lazy_closure.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ protected function createProxy($class, \Closure $factory)
5757
*/
5858
protected static function getClosure1Service($container, $lazyLoad = true)
5959
{
60-
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(...);
60+
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(...);
6161
}
6262

6363
/**
@@ -67,6 +67,6 @@ protected static function getClosure1Service($container, $lazyLoad = true)
6767
*/
6868
protected static function getClosure2Service($container, $lazyLoad = true)
6969
{
70-
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(...);
70+
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(...);
7171
}
7272
}

Tests/Fixtures/php/services_almost_circular_private.php

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -373,15 +373,13 @@ protected static function getManager2Service($container)
373373
*/
374374
protected static function getManager3Service($container, $lazyLoad = true)
375375
{
376-
$a = ($container->services['listener3'] ?? self::getListener3Service($container));
376+
$a = ($container->privates['connection3'] ?? self::getConnection3Service($container));
377377

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

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

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

482+
/**
483+
* Gets the private 'connection3' shared service.
484+
*
485+
* @return \stdClass
486+
*/
487+
protected static function getConnection3Service($container)
488+
{
489+
$container->privates['connection3'] = $instance = new \stdClass();
490+
491+
$instance->listener = [($container->services['listener3'] ?? self::getListener3Service($container))];
492+
493+
return $instance;
494+
}
495+
496+
/**
497+
* Gets the private 'connection4' shared service.
498+
*
499+
* @return \stdClass
500+
*/
501+
protected static function getConnection4Service($container)
502+
{
503+
$container->privates['connection4'] = $instance = new \stdClass();
504+
505+
$instance->listener = [($container->services['listener4'] ?? self::getListener4Service($container))];
506+
507+
return $instance;
508+
}
509+
484510
/**
485511
* Gets the private 'doctrine.listener' shared service.
486512
*
@@ -572,13 +598,13 @@ protected static function getMailerInline_TransportFactory_AmazonService($contai
572598
*/
573599
protected static function getManager4Service($container, $lazyLoad = true)
574600
{
575-
$a = new \stdClass();
601+
$a = ($container->privates['connection4'] ?? self::getConnection4Service($container));
576602

577-
$container->privates['manager4'] = $instance = new \stdClass($a);
578-
579-
$a->listener = [($container->services['listener4'] ?? self::getListener4Service($container))];
603+
if (isset($container->privates['manager4'])) {
604+
return $container->privates['manager4'];
605+
}
580606

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

584610
/**

Tests/Fixtures/php/services_almost_circular_public.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ protected static function getDispatcher2Service($container, $lazyLoad = true)
259259
{
260260
$container->services['dispatcher2'] = $instance = new \stdClass();
261261

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

264264
return $instance;
265265
}
@@ -820,4 +820,20 @@ protected static function getManager4Service($container, $lazyLoad = true)
820820

821821
return $container->privates['manager4'] = new \stdClass($a);
822822
}
823+
824+
/**
825+
* Gets the private 'subscriber2' shared service.
826+
*
827+
* @return \stdClass
828+
*/
829+
protected static function getSubscriber2Service($container)
830+
{
831+
$a = ($container->services['manager2'] ?? self::getManager2Service($container));
832+
833+
if (isset($container->privates['subscriber2'])) {
834+
return $container->privates['subscriber2'];
835+
}
836+
837+
return $container->privates['subscriber2'] = new \stdClass($a);
838+
}
823839
}

Tests/Fixtures/php/services_wither_lazy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ protected static function getWitherService($container, $lazyLoad = true)
6161

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

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

6666
$instance = $instance->withFoo1($a);
6767
$instance = $instance->withFoo2($a);

0 commit comments

Comments
 (0)