8000 feature #46741 [DependencyInjection] Allow using ghost objects for la… · symfony/symfony@bbf25d6 · GitHub
[go: up one dir, main page]

Skip to content

Commit bbf25d6

Browse files
feature #46741 [DependencyInjection] Allow using ghost objects for lazy loading services (nicolas-grekas)
This PR was merged into the 6.2 branch. Discussion ---------- [DependencyInjection] Allow using ghost objects for lazy loading services | Q | A | ------------- | --- | Branch? | 6.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - This PR is a subset of #46458 that contains the needed API changes to allow lazy-proxy dumpers to use ghost objects. The main change of this PR is adding argument `bool &$asGhostObject = null` to LazyProxy's `DumperInterface`. The rest is the consequence of that new capability. The changes on ProxyManager are all minor: refactoring the code to make it more flexible (as proved by #46458) and adding the new argument but not doing anything with it (yet.) Commits ------- 2997693 [DependencyInjection] Allow using ghost objects for lazy loading services
2 parents eda7eb3 + 2997693 commit bbf25d6

22 files changed

+387
-111
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Bridge\ProxyManager\Internal;
13+
14+
use ProxyManager\Configuration;
15+
16+
/**
17+
* @internal
18+
*/
19+
trait LazyLoadingFactoryTrait
20+
{
21+
private readonly ProxyGenerator $generator;
22+
23+
public function __construct(Configuration $config, ProxyGenerator $generator)
24+
{
25+
parent::__construct($config);
26+
$this->generator = $generator;
27+
}
28+
29+
public function getGenerator(): ProxyGenerator
30+
{
31+
return $this->generator;
32+
}
33+
}
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,24 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
12+
namespace Symfony\Bridge\ProxyManager\Internal;
1313

1414
use Laminas\Code\Generator\ClassGenerator;
15-
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator;
15+
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
16+
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
1617
use Symfony\Component\DependencyInjection\Definition;
1718

1819
/**
1920
* @internal
2021
*/
21-
class LazyLoadingValueHolderGenerator extends BaseGenerator
22+
class ProxyGenerator implements ProxyGeneratorInterface
2223
{
2324
/**
2425
* {@inheritdoc}
2526
*/
2627
public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []): void
2728
{
28-
parent::generate($originalClass, $classGenerator, $proxyOptions);
29+
(new LazyLoadingValueHolderGenerator())->generate($originalClass, $classGenerator, $proxyOptions);
2930

3031
foreach ($classGenerator->getMethods() as $method) {
3132
if (str_starts_with($originalClass->getFilename(), __FILE__)) {
@@ -43,7 +44,11 @@ public function generate(\ReflectionClass $originalClass, ClassGenerator $classG
4344
public function getProxifiedClass(Definition $definition): ?string
4445
{
4546
if (!$definition->hasTag('proxy')) {
46-
return ($class = $definition->getClass()) && (class_exists($class) || interface_exists($class, false)) ? $class : null;
47+
if (!($class = $definition->getClass()) || !(class_exists($class) || interface_exists($class, false))) {
48+
return null;
49+
}
50+
51+
return (new \ReflectionClass($class))->name;
4752
}
4853
if (!$definition->isLazy()) {
4954
throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": setting the "proxy" tag on a service requires it to be "lazy".', $definition->getClass()));

src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator;
1313

1414
use ProxyManager\Configuration;
15+
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
1516
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
1617
use ProxyManager\Proxy\LazyLoadingInterface;
18+
use Symfony\Bridge\ProxyManager\Internal\LazyLoadingFactoryTrait;
19+
use Symfony\Bridge\ProxyManager\Internal\ProxyGenerator;
1720
use Symfony\Component\DependencyInjection\ContainerInterface;
1821
use Symfony\Component\DependencyInjection\Definition;
1922
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
@@ -25,34 +28,37 @@
2528
*/
2629
class RuntimeInstantiator implements InstantiatorInterface
2730
{
28-
private LazyLoadingValueHolderFactory $factory;
31+
private Configuration $config;
32+
private ProxyGenerator $generator;
2933

3034
public function __construct()
3135
{
32-
$config = new Configuration();
33-
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
34-
35-
$this->factory = new LazyLoadingValueHolderFactory($config);
36+
$this->config = new Configuration();
37+
$this->config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
38+
$this->generator = new ProxyGenerator();
3639
}
3740

3841
/**
3942
* {@inheritdoc}
4043
*/
4144
public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object
4245
{
43-
return $this->factory->createProxy(
44-
$this->factory->getGenerator()->getProxifiedClass($definition),
45-
function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
46-
$wrappedInstance = $realInstantiator();
47-
48-
$proxy->setProxyInitializer(null);
49-
50-
return true;
51-
},
52-
[
53-
'fluentSafe' => $definition->hasTag('proxy'),
54-
'skipDestructor' => true,
55-
]
56-
);
46+
$proxifiedClass = new \ReflectionClass($this->generator->getProxifiedClass($definition));
47+
48+
$factory = new class($this->config, $this->generator) extends LazyLoadingValueHolderFactory {
49+
use LazyLoadingFactoryTrait;
50+
};
51+
52+
$initializer = static function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
53+
$wrappedInstance = $realInstantiator();
54+
$proxy->setProxyInitializer(null);
55+
56+
return true;
57+
};
58+
59+
return $factory->createProxy($proxifiedClass->name, $initializer, [
60+
'fluentSafe' => $definition->hasTag('proxy'),
61+
'skipDestructor' => true,
62+
]);
5763
}
5864
}

src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Laminas\Code\Generator\ClassGenerator;
1515
use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy;
16+
use Symfony\Bridge\ProxyManager\Internal\ProxyGenerator;
1617
use Symfony\Component\DependencyInjection\Definition;
1718
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface;
1819

@@ -26,21 +27,23 @@
2627
class ProxyDumper implements DumperInterface
2728
{
2829
private string $salt;
29-
private LazyLoadingValueHolderGenerator $proxyGenerator;
30+
private ProxyGenerator $proxyGenerator;
3031
private BaseGeneratorStrategy $classGenerator;
3132

3233
public function __construct(string $salt = '')
3334
{
3435
$this->salt = $salt;
35-
$this->proxyGenerator = new LazyLoadingValueHolderGenerator();
36+
$this->proxyGenerator = new ProxyGenerator();
3637
$this->classGenerator = new BaseGeneratorStrategy();
3738
}
3839

3940
/**
4041
* {@inheritdoc}
4142
*/
42-
public function isProxyCandidate(Definition $definition): bool
43+
public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null): bool
4344
{
45+
$asGhostObject = false;
46+
4447
return ($definition->isLazy() || $definition->hasTag('proxy')) && $this->proxyGenerator->getProxifiedClass($definition);
4548
}
4649

@@ -55,10 +58,11 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $
5558
$instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true));
5659
}
5760

58-
$proxyClass = $this->getProxyClassName($definition);
61+
$proxifiedClass = new \ReflectionClass($this->proxyGenerator->getProxifiedClass($definition));
62+
$proxyClass = $this->getProxyClassName($proxifiedClass->name);
5963

6064
return <<<EOF
61-
if (\$lazyLoad) {
65+
if (true === \$lazyLoad) {
6266
$instantiation \$this->createProxy('$proxyClass', function () {
6367
return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
6468
\$wrappedInstance = $factoryCode;
@@ -85,20 +89,15 @@ public function getProxyCode(Definition $definition): string
8589
return $code;
8690
}
8791

88-
/**
89-
* Produces the proxy class name for the given definition.
90-
*/
91-
private function getProxyClassName(Definition $definition): string
92+
private function getProxyClassName(string $class): string
9293
{
93-
$class = $this->proxyGenerator->getProxifiedClass($definition);
94-
95-
return preg_replace('/^.*\\\\/', '', $class).'_'.$this->getIdentifierSuffix($definition);
94+
return preg_replace('/^.*\\\\/', '', $class).'_'.substr(hash('sha256', $class.$this->salt), -7);
9695
}
9796

9897
private function generateProxyClass(Definition $definition): ClassGenerator
9998
{
100-
$generatedClass = new ClassGenerator($this->getProxyClassName($definition));
10199
$class = $this->proxyGenerator->getProxifiedClass($definition);
100+
$generatedClass = new ClassGenerator($this->getProxyClassName($class));
102101

103102
$this->proxyGenerator->generate(new \ReflectionClass($class), $generatedClass, [
104103
'fluentSafe' => $definition->hasTag('proxy'),
@@ -107,11 +106,4 @@ private function generateProxyClass(Definition $definition): ClassGenerator
107106

108107
return $generatedClass;
109108
}
110-
111-
private function getIdentifierSuffix(Definition $definition): string
112-
{
113-
$class = $this->proxyGenerator->getProxifiedClass($definition);
114-
115-
return substr(hash('sha256', $class.$this->salt), -7);
116-
}
117109
}

src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class LazyServiceProjectServiceContainer extends Container
55
{%a
66
protected function getFooService($lazyLoad = true)
77
{
8-
if ($lazyLoad) {
8+
if (true === $lazyLoad) {
99
return $this->services['foo'] = $this->createProxy('stdClass_%s', function () {
1010
return %S\stdClass_%s(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
1111
$wrappedInstance = $this->getFooService(false);

src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
public function getFooService($lazyLoad = true)
99
{
10-
if ($lazyLoad) {
10+
if (true === $lazyLoad) {
1111
return $this->privates['foo'] = $this->createProxy('SunnyInterface_1eff735', function () {
1212
return \SunnyInterface_1eff735::staticProxyConstructor(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
1313
$wrappedInstance = $this->getFooService(false);

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add argument `&$asGhostObject` to LazyProxy's `DumperInterface` to allow using ghost objects for lazy loading services
8+
49
6.1
510
---
611

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
4646
use Symfony\Component\ExpressionLanguage\Expression;
4747
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
48+
use Symfony\Component\VarExporter\Hydrator;
4849

4950
/**
5051
* ContainerBuilder is a DI container that provides an API to easily describe services.
@@ -974,7 +975,7 @@ public function findDefinition(string $id): Definition
974975
* @throws RuntimeException When the service is a synthetic service
975976
* @throws InvalidArgumentException When configure callable is not callable
976977
*/
977-
private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool $tryProxy = true): mixed
978+
private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool|object $tryProxy = true): mixed
978979
{
979980
if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) {
980981
return $inlineServices[$h];
@@ -993,12 +994,12 @@ private function createService(Definition $definition, array &$inlineServices, b
993994
trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
994995
}
995996

996-
if ($tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
997+
if (true === $tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
997998
$proxy = $proxy->instantiateProxy(
998999
$this,
9991000
$definition,
1000-
$id, function () use ($definition, &$inlineServices, $id) {
1001-
return $this->createService($definition, $inlineServices, true, $id, false);
1001+
$id, function ($proxy = false) use ($definition, &$inlineServices, $id) {
1002+
return $this->createService($definition, $inlineServices, true, $id, $proxy);
10021003
}
10031004
);
10041005
$this->shareService($definition, $proxy, $id, $inlineServices);
@@ -1029,13 +1030,21 @@ private function createService(Definition $definition, array &$inlineServices, b
10291030

10301031
$arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($arguments)), $inlineServices, $isConstructorArgument);
10311032

1032-
if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) {
1033+
if (null !== $id && $definition->isShared() && isset($this->services[$id]) && (true === $tryProxy || !$definition->isLazy())) {
10331034
return $this->services[$id];
10341035
}
10351036

10361037
if (null !== $factory) {
10371038
$service = $factory(...$arguments);
10381039

1040+
if (\is_object($tryProxy)) {
1041+
if (\get_class($service) !== $definition->getClass()) {
1042+
throw new LogicException(sprintf('Lazy service of type "%s" cannot be hydrated because its factory returned an unexpected instance of "%s". Try adding the "proxy" tag to the corresponding service definition with attribute "interface" set to "%1$s".', $definition->getClass(), get_debug_type($service)));
1043+
}
1044+
1045+
$tryProxy = Hydrator::hydrate($tryProxy, (array) $service);
1046+
}
1047+
10391048
if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) {
10401049
$r = new \ReflectionClass($factory[0]);
10411050

@@ -1046,7 +1055,15 @@ private function createService(Definition $definition, array &$inlineServices, b
10461055
} else {
10471056
$r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
10481057

1049-
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs(array_values($arguments));
1058+
if (\is_object($tryProxy)) {
1059+
if ($r->getConstructor()) {
1060+
$tryProxy->__construct(...array_values($arguments));
1061+
}
1062+
1063+
$service = $tryProxy;
1064+
} else {
1065+
$service = $r->getConstructor() ? $r->newInstanceArgs(array_values($arguments)) : $r->newInstance();
1066+
}
10501067

10511068
if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
10521069
trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name);
@@ -1060,7 +1077,7 @@ private function createService(Definition $definition, array &$inlineServices, b
10601077
}
10611078
}
10621079

1063-
if (null === $lastWitherIndex && ($tryProxy || !$definition->isLazy())) {
1080+
if (null === $lastWitherIndex && (true === $tryProxy || !$definition->isLazy())) {
10641081
// share only if proxying failed, or if not a proxy, and if no withers are found
10651082
$this->shareService($definition, $service, $id, $inlineServices);
10661083
}
@@ -1073,7 +1090,7 @@ private function createService(Definition $definition, array &$inlineServices, b
10731090
foreach ($definition->getMethodCalls() as $k => $call) {
10741091
$service = $this->callMethod($service, $call, $inlineServices);
10751092

1076-
if ($lastWitherIndex === $k && ($tryProxy || !$definition->isLazy())) {
1093+
if ($lastWitherIndex === $k && (true === $tryProxy || !$definition->isLazy())) {
10771094
// share only if proxying failed, or if not a proxy, and this is the last wither
10781095
$this->shareService($definition, $service, $id, $inlineServices);
10791096
}

0 commit comments

Comments
 (0)
0