From ef2626f9e56c60e1b51a6ff1027971e177dce4d1 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 15 Jul 2023 00:29:02 +0200 Subject: [PATCH] [DependencyInjection] Fix fetching lazy non-shared services multiple times --- .../DependencyInjection/Dumper/PhpDumper.php | 2 + .../Tests/Dumper/PhpDumperTest.php | 69 ++++++++++++++- .../php/services_non_shared_lazy_public.php | 79 +++++++++++++++++ .../php/services_wither_lazy_non_shared.php | 88 +++++++++++++++++++ 4 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_public.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 64c2b41b5fa72..99b635c2e146f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -900,6 +900,8 @@ protected function {$methodName}($lazyInitialization) if ($asFile) { $code .= "fn () => self::do(\$container);\n\n"; + } elseif ($definition->isPublic()) { + $code .= sprintf("fn () => \$this->%s();\n\n", $methodName); } else { $code .= sprintf("\$this->%s(...);\n\n", $methodName); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 814d4c31705a1..1f6a610903b19 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -692,6 +692,42 @@ public function testInlinedDefinitionReferencingServiceContainer() $this->assertStringEqualsFile(self::$fixturesPath.'/php/services13.php', $dumper->dump(), '->dump() dumps inline definitions which reference service_container'); } + public function testNonSharedLazy() + { + $container = new ContainerBuilder(); + + $container + ->register('foo', Foo::class) + ->setFile(realpath(self::$fixturesPath.'/includes/foo_lazy.php')) + ->setShared(false) + ->setLazy(true) + ->setPublic(true); + + $container->compile(); + $dumper = new PhpDumper($container); + $dump = $dumper->dump([ + 'class' => 'Symfony_DI_PhpDumper_Service_Non_Shared_Lazy', + 'file' => __DIR__, + 'inline_factories_parameter' => false, + 'inline_class_loader_parameter' => false, + ]); + $this->assertStringEqualsFile( + self::$fixturesPath.'/php/services_non_shared_lazy_public.php', + '\\' === \DIRECTORY_SEPARATOR ? str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump) : $dump + ); + eval('?>'.$dump); + + $container = new \Symfony_DI_PhpDumper_Service_Non_Shared_Lazy(); + + $foo1 = $container->get('foo'); + $this->assertTrue($foo1->resetLazyObject()); + + $foo2 = $container->get('foo'); + $this->assertTrue($foo2->resetLazyObject()); + + $this->assertNotSame($foo1, $foo2); + } + /** * @testWith [false] * [true] @@ -700,7 +736,7 @@ public function testNonSharedLazyDefinitionReferences(bool $asGhostObject) { $container = new ContainerBuilder(); $container->register('foo', 'stdClass')->setShared(false)->setLazy(true); - $container->register('bar', 'stdClass')->addArgument(new Reference('foo', ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, false))->setPublic(true); + $container->register('bar', 'stdClass')->addArgument(new Reference('foo', ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE))->setPublic(true); $container->compile(); $dumper = new PhpDumper($container); @@ -1459,6 +1495,37 @@ public function testLazyWither() $this->assertTrue($wither->resetLazyObject()); } + public function testLazyWitherNonShared() + { + $container = new ContainerBuilder(); + $container->register(Foo::class); + + $container + ->register('wither', Wither::class) + ->setShared(false) + ->setLazy(true) + ->setPublic(true) + ->setAutowired(true); + + $container->compile(); + $dumper = new PhpDumper($container); + $dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_Wither_Lazy_Non_Shared']); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_wither_lazy_non_shared.php', $dump); + eval('?>'.$dump); + + $container = new \Symfony_DI_PhpDumper_Service_Wither_Lazy_Non_Shared(); + + $wither1 = $container->get('wither'); + $this->assertInstanceOf(Foo::class, $wither1->foo); + $this->assertTrue($wither1->resetLazyObject()); + + $wither2 = $container->get('wither'); + $this->assertInstanceOf(Foo::class, $wither2->foo); + $this->assertTrue($wither2->resetLazyObject()); + + $this->assertNotSame($wither1, $wither2); + } + public function testWitherWithStaticReturnType() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_public.php new file mode 100644 index 0000000000000..13190f1c7bd6b --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_public.php @@ -0,0 +1,79 @@ +services = $this->privates = []; + $this->methodMap = [ + 'foo' => 'getFooService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'foo' service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Foo + */ + protected function getFooService($lazyLoad = true) + { + $this->factories['foo'] ??= fn () => $this->getFooService(); + + if (true === $lazyLoad) { + return $this->createProxy('FooGhostCf082ac', fn () => \FooGhostCf082ac::createLazyGhost($this->getFooService(...))); + } + + static $include = true; + + if ($include) { + include_once __DIR__.'/Fixtures/includes/foo_lazy.php'; + + $include = false; + } + + return $lazyLoad; + } +} + +class FooGhostCf082ac extends \Symfony\Component\DependencyInjection\Tests\Compiler\Foo implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyGhostTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php new file mode 100644 index 0000000000000..60bdc4f000822 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php @@ -0,0 +1,88 @@ +services = $this->privates = []; + $this->methodMap = [ + 'wither' => 'getWitherService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + public function getRemovedIds(): array + { + return [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true, + ]; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'wither' autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Wither + */ + protected function getWitherService($lazyLoad = true) + { + $this->factories['wither'] ??= fn () => $this->getWitherService(); + + if (true === $lazyLoad) { + return $this->createProxy('WitherProxy8cb632f', fn () => \WitherProxy8cb632f::createLazyProxy(fn () => $this->getWitherService(false))); + } + + $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); + + $a = ($this->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()); + + $instance = $instance->withFoo1($a); + $instance = $instance->withFoo2($a); + $instance->setFoo($a); + + return $instance; + } +} + +class WitherProxy8cb632f extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyProxyTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = [ + 'foo' => [parent::class, 'foo', null], + ]; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);