Skip to content

[DependencyInjection] Generate different classes for ghost objects and virtual proxies #48522

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 1 commit into from
Dec 7, 2022
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 @@ -545,10 +545,10 @@ private function generateProxyClasses(): array
if (!$definition = $this->isProxyCandidate($definition, $asGhostObject, $id)) {
continue;
}
if (isset($alreadyGenerated[$class = $definition->getClass()])) {
if (isset($alreadyGenerated[$asGhostObject][$class = $definition->getClass()])) {
continue;
}
$alreadyGenerated[$class] = true;
$alreadyGenerated[$asGhostObject][$class] = true;
// register class' reflector for resource tracking
$this->container->getReflectionClass($class);
if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition, $id)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
use Symfony\Component\VarExporter\LazyGhostTrait;

/**
* @author Nicolas Grekas <p@tchwork.com>
Expand All @@ -25,10 +25,14 @@ public function instantiateProxy(ContainerInterface $container, Definition $defi
{
$dumper = new LazyServiceDumper();

if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $class), false)) {
if (!$dumper->isProxyCandidate($definition, $asGhostObject, $id)) {
throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id));
}

if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $asGhostObject, $class), false)) {
eval($dumper->getProxyCode($definition, $id));
}

return isset(class_uses($proxyClass)[LazyGhostTrait::class]) ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator);
return $asGhostObject ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,10 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $
$instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true));
}

$proxyClass = $this->getProxyClass($definition);
$asGhostObject = str_contains($factoryCode, '$proxy');
$proxyClass = $this->getProxyClass($definition, $asGhostObject);

if (!str_contains($factoryCode, '$proxy')) {
if (!$asGhostObject) {
return <<<EOF
if (true === \$lazyLoad) {
$instantiation \$this->createProxy('$proxyClass', fn () => \\$proxyClass::createLazyProxy(fn () => $factoryCode));
Expand Down Expand Up @@ -104,7 +105,7 @@ public function getProxyCode(Definition $definition, string $id = null): string
if (!$this->isProxyCandidate($definition, $asGhostObject, $id)) {
throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id ?? $definition->getClass()));
}
$proxyClass = $this->getProxyClass($definition, $class);
$proxyClass = $this->getProxyClass($definition, $asGhostObject, $class);

if ($asGhostObject) {
try {
Expand Down Expand Up @@ -142,10 +143,12 @@ public function getProxyCode(Definition $definition, string $id = null): string
}
}

public function getProxyClass(Definition $definition, \ReflectionClass &$class = null): string
public function getProxyClass(Definition $definition, bool $asGhostObject, \ReflectionClass &$class = null): string
{
$class = new \ReflectionClass($definition->getClass());

return preg_replace('/^.*\\\\/', '', $class->name).'_'.substr(hash('sha256', $this->salt.'+'.$class->name), -7);
return preg_replace('/^.*\\\\/', '', $class->name)
.($asGhostObject ? 'Ghost' : 'Proxy')
.ucfirst(substr(hash('sha256', $this->salt.'+'.$class->name), -7));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -772,24 +772,18 @@ public function testCircularReferenceAllowanceForLazyServices()
$dumper->dump();
}

/**
* @testWith [false]
* [true]
*/
public function testDedupLazyProxy(bool $asGhostObject)
public function testDedupLazyProxy()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setLazy(true)->setPublic(true);
$container->register('bar', 'stdClass')->setLazy(true)->setPublic(true);
$container->register('baz', 'stdClass')->setLazy(true)->setPublic(true)->setFactory('foo_bar');
$container->register('buz', 'stdClass')->setLazy(true)->setPublic(true)->setFactory('foo_bar');
$container->compile();

$dumper = new PhpDumper($container);

if (!$asGhostObject) {
$dumper->setProxyDumper(new \DummyProxyDumper());
}

$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_dedup_lazy'.($asGhostObject ? '_ghost' : '_proxy').'.php', $dumper->dump());
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_dedup_lazy.php', $dumper->dump());
}

public function testLazyArgumentProvideGenerator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ namespace Container%s;

include_once $this->targetDir.''.'/Fixtures/includes/foo.php';

class FooClass_2b16075 extends \Bar\FooClass implements \Symfony\Component\VarExporter\LazyObjectInterface
class FooClassGhost2b16075 extends \Bar\FooClass implements \Symfony\Component\VarExporter\LazyObjectInterface
%A

if (!\class_exists('FooClass_%s', false)) {
\class_alias(__NAMESPACE__.'\\FooClass_%s', 'FooClass_%s', false);
if (!\class_exists('FooClassGhost2b16075', false)) {
\class_alias(__NAMESPACE__.'\\FooClassGhost2b16075', 'FooClassGhost2b16075', false);
}

[Container%s/ProjectServiceContainer.php] => <?php
Expand Down Expand Up @@ -78,7 +78,7 @@ class ProjectServiceContainer extends Container
protected function getLazyFooService($lazyLoad = true)
{
if (true === $lazyLoad) {
return $this->services['lazy_foo'] = $this->createProxy('FooClass_2b16075', fn () => \FooClass_2b16075::createLazyGhost($this->getLazyFooService(...)));
return $this->services['lazy_foo'] = $this->createProxy('FooClassGhost2b16075', fn () => \FooClassGhost2b16075::createLazyGhost($this->getLazyFooService(...)));
}

include_once $this->targetDir.''.'/Fixtures/includes/foo_lazy.php';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public function __construct()
$this->services = $this->privates = [];
$this->methodMap = [
'bar' => 'getBarService',
'baz' => 'getBazService',
'buz' => 'getBuzService',
'foo' => 'getFooService',
];

Expand Down Expand Up @@ -50,12 +52,40 @@ protected function createProxy($class, \Closure $factory)
protected function getBarService($lazyLoad = true)
{
if (true === $lazyLoad) {
return $this->services['bar'] = $this->createProxy('stdClass_5a8a5eb', fn () => \stdClass_5a8a5eb::createLazyGhost($this->getBarService(...)));
return $this->services['bar'] = $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getBarService(...)));
}

return $lazyLoad;
}

/**
* Gets the public 'baz' shared service.
*
* @return \stdClass
*/
protected function getBazService($lazyLoad = true)
{
if (true === $lazyLoad) {
return $this->services['baz'] = $this->createProxy('stdClassProxy5a8a5eb', fn () => \stdClassProxy5a8a5eb::createLazyProxy(fn () => $this->getBazService(false)));
}

return \foo_bar();
}

/**
* Gets the public 'buz' shared service.
*
* @return \stdClass
*/
protected function getBuzService($lazyLoad = true)
{
if (true === $lazyLoad) {
return $this->services['buz'] = $this->createProxy('stdClassProxy5a8a5eb', fn () => \stdClassProxy5a8a5eb::createLazyProxy(fn () => $this->getBuzService(false)));
}

return \foo_bar();
}

/**
* Gets the public 'foo' shared service.
*
Expand All @@ -64,14 +94,14 @@ protected function getBarService($lazyLoad = true)
protected function getFooService($lazyLoad = true)
{
if (true === $lazyLoad) {
return $this->services['foo'] = $this->createProxy('stdClass_5a8a5eb', fn () => \stdClass_5a8a5eb::createLazyGhost($this->getFooService(...)));
return $this->services['foo'] = $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getFooService(...)));
}

return $lazyLoad;
}
}

class stdClass_5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface
class stdClassGhost5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyGhostTrait;

Expand All @@ -82,3 +112,18 @@ class stdClass_5a8a5eb extends \stdClass implements \Symfony\Component\VarExport
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);

class stdClassProxy5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyProxyTrait;

private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', 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);

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class getNonSharedFooService extends ProjectServiceContainer
$container->factories['non_shared_foo'] ??= fn () => self::do($container);

if (true === $lazyLoad) {
return $container->createProxy('FooLazyClass_f814e3a', fn () => \FooLazyClass_f814e3a::createLazyGhost(fn ($proxy) => self::do($container, $proxy)));
return $container->createProxy('FooLazyClassGhostF814e3a', fn () => \FooLazyClassGhostF814e3a::createLazyGhost(fn ($proxy) => self::do($container, $proxy)));
}

static $include = true;
Expand All @@ -37,11 +37,11 @@ class getNonSharedFooService extends ProjectServiceContainer
}
}

[Container%s/FooLazyClass_f814e3a.php] => <?php
[Container%s/FooLazyClassGhostF814e3a.php] => <?php

namespace Container%s;

class FooLazyClass_f814e3a extends \Bar\FooLazyClass implements \Symfony\Component\VarExporter\LazyObjectInterface
class FooLazyClassGhostF814e3a extends \Bar\FooLazyClass implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyGhostTrait;

Expand All @@ -53,8 +53,8 @@ class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);

if (!\class_exists('FooLazyClass_f814e3a', false)) {
\class_alias(__NAMESPACE__.'\\FooLazyClass_f814e3a', 'FooLazyClass_f814e3a', false);
if (!\class_exists('FooLazyClassGhostF814e3a', false)) {
\class_alias(__NAMESPACE__.'\\FooLazyClassGhostF814e3a', 'FooLazyClassGhostF814e3a', false);
}

[Container%s/ProjectServiceContainer.php] => <?php
Expand Down Expand Up @@ -141,7 +141,7 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {

require dirname(__DIR__, %d).'%svendor/autoload.php';
(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null);
require __DIR__.'/Container%s/FooLazyClass_f814e3a.php';
require __DIR__.'/Container%s/FooLazyClassGhostF814e3a.php';
require __DIR__.'/Container%s/getNonSharedFooService.php';

$classes = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ protected function getFooService($lazyLoad = true)
$this->factories['service_container']['foo'] ??= $this->getFooService(...);

if (true === $lazyLoad) {
return $this->createProxy('stdClass_5a8a5eb', fn () => \stdClass_5a8a5eb::createLazyGhost($this->getFooService(...)));
return $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getFooService(...)));
}

return $lazyLoad;
}
}

class stdClass_5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface
class stdClassGhost5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyGhostTrait;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ protected function createProxy($class, \Closure $factory)
protected function getWitherService($lazyLoad = true)
{
if (true === $lazyLoad) {
return $this->services['wither'] = $this->createProxy('Wither_94fa281', fn () => \Wither_94fa281::createLazyProxy(fn () => $this->getWitherService(false)));
return $this->services['wither'] = $this->createProxy('WitherProxy94fa281', fn () => \WitherProxy94fa281::createLazyProxy(fn () => $this->getWitherService(false)));
}

$instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither();
Expand All @@ -71,7 +71,7 @@ protected function getWitherService($lazyLoad = true)
}
}

class Wither_94fa281 extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface
class WitherProxy94fa281 extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyProxyTrait;

Expand Down