Skip to content

[DependencyInjection] Fix fetching lazy non-shared services multiple times #50985

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
[DependencyInjection] Fix fetching lazy non-shared services multiple …
…times
  • Loading branch information
HypeMC committed Jul 16, 2023
commit ef2626f9e56c60e1b51a6ff1027971e177dce4d1
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

/**
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
*/
class Symfony_DI_PhpDumper_Service_Non_Shared_Lazy extends Container
{
protected $parameters = [];

public function __construct()
{
$this->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);
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

/**
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
*/
class Symfony_DI_PhpDumper_Service_Wither_Lazy_Non_Shared extends Container
{
protected $parameters = [];

public function __construct()
{
$this->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);