Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3bd9bd8

Browse files
[DependencyInjection] Use lazy-loading ghost object proxies out of the box
1 parent 135fc98 commit 3bd9bd8

File tree

19 files changed

+278
-48
lines changed

19 files changed

+278
-48
lines changed

src/Symfony/Bridge/Doctrine/ManagerRegistry.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ProxyManager\Proxy\LazyLoadingInterface;
1717
use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
1818
use Symfony\Component\DependencyInjection\Container;
19+
use Symfony\Component\VarExporter\LazyGhostObjectInterface;
1920

2021
/**
2122
* References Doctrine connections and entity/document managers.
@@ -47,6 +48,11 @@ protected function resetService($name): void
4748
}
4849
$manager = $this->container->get($name);
4950

51+
if ($manager instanceof LazyGhostObjectInterface) {
52+
$manager->resetLazyGhostObject();
53+
54+
return;
55+
}
5056
if (!$manager instanceof LazyLoadingInterface) {
5157
throw new \LogicException('Resetting a non-lazy manager service is not supported. '.(interface_exists(LazyLoadingInterface::class) && class_exists(RuntimeInstantiator::class) ? sprintf('Declare the "%s" service as lazy.', $name) : 'Try running "composer require symfony/proxy-manager-bridge".'));
5258
}

src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use PHPUnit\Framework\TestCase;
1717
use ProxyManager\Proxy\LazyLoadingInterface;
1818
use ProxyManagerBridgeFooClass;
19-
use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
2019
use Symfony\Component\DependencyInjection\ContainerBuilder;
2120

2221
/**
@@ -31,10 +30,8 @@ public function testCreateProxyServiceWithRuntimeInstantiator()
3130
{
3231
$builder = new ContainerBuilder();
3332

34-
$builder->setProxyInstantiator(new RuntimeInstantiator());
35-
3633
$builder->register('foo1', ProxyManagerBridgeFooClass::class)->setFile(__DIR__.'/Fixtures/includes/foo.php')->setPublic(true);
37-
$builder->getDefinition('foo1')->setLazy(true);
34+
$builder->getDefinition('foo1')->setLazy(true)->addTag('proxy', ['interface' => ProxyManagerBridgeFooClass::class]);
3835

3936
$builder->compile();
4037

src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use ProxyManager\Proxy\LazyLoadingInterface;
16-
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
1716
use Symfony\Component\DependencyInjection\ContainerBuilder;
1817
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
1918

@@ -63,13 +62,11 @@ private function dumpLazyServiceProjectServiceContainer()
6362
$container = new ContainerBuilder();
6463

6564
$container->register('foo', 'stdClass')->setPublic(true);
66-
$container->getDefinition('foo')->setLazy(true);
65+
$container->getDefinition('foo')->setLazy(true)->addTag('proxy', ['interface' => 'stdClass']);
6766
$container->compile();
6867

6968
$dumper = new PhpDumper($container);
7069

71-
$dumper->setProxyDumper(new ProxyDumper());
72-
7370
return $dumper->dump(['class' => 'LazyServiceProjectServiceContainer']);
7471
}
7572
}

src/Symfony/Bridge/ProxyManager/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
"require": {
1919
"php": ">=8.1",
2020
"friendsofphp/proxy-manager-lts": "^1.0.2",
21-
"symfony/dependency-injection": "^5.4|^6.0"
21+
"symfony/dependency-injection": "^6.2"
2222
},
2323
"require-dev": {
24-
"symfony/config": "^5.4|^6.0"
24+
"symfony/config": "^6.1"
2525
},
2626
"autoload": {
2727
"psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" },

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ CHANGELOG
44
6.2
55
---
66

7+
* Use lazy-loading ghost object proxies out of the box
78
* Add argument `&$asGhostObject` to LazyProxy's `DumperInterface` to allow using ghost objects for lazy loading services
9+
* Deprecate `RealServiceInstantiator`
810

911
6.1
1012
---

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
4040
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
4141
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
42+
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\LazyServiceInstantiator;
4243
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
4344
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
4445
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@@ -86,7 +87,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
8687

8788
private Compiler $compiler;
8889
private bool $trackResources;
89-
private ?InstantiatorInterface $proxyInstantiator = null;
90+
private InstantiatorInterface $proxyInstantiator;
9091
private ExpressionLanguage $expressionLanguage;
9192

9293
/**
@@ -994,7 +995,7 @@ private function createService(Definition $definition, array &$inlineServices, b
994995
trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
995996
}
996997

997-
if (true === $tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
998+
if (true === $tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator ??= new LazyServiceInstantiator()) || $proxy instanceof RealServiceInstantiator) {
998999
$proxy = $proxy->instantiateProxy(
9991000
$this,
10001001
$definition,

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
3333
use Symfony\Component\DependencyInjection\ExpressionLanguage;
3434
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
35+
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
3536
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
3637
use Symfony\Component\DependencyInjection\Loader\FileLoader;
3738
use Symfony\Component\DependencyInjection\Parameter;
@@ -89,6 +90,7 @@ class PhpDumper extends Dumper
8990
private string $serviceLocatorTag;
9091
private array $exportedVariables = [];
9192
private string $baseClass;
93+
private string $class;
9294
private ProxyDumper $proxyDumper;
9395

9496
/**
@@ -154,6 +156,7 @@ public function dump(array $options = []): string|array
154156
$this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']);
155157
$this->inlineRequires = $options['inline_class_loader_parameter'] && ($this->container->hasParameter($options['inline_class_loader_parameter']) ? $this->container->getParameter($options['inline_class_loader_parameter']) : $options['debug']);
156158
$this->serviceLocatorTag = $options['service_locator_tag'];
159+
$this->class = $options['class'];
157160

158161
if (!str_starts_with($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) {
159162
$baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass);
@@ -401,7 +404,7 @@ class %s extends {$options['class']}
401404
*/
402405
private function getProxyDumper(): ProxyDumper
403406
{
404-
return $this->proxyDumper ??= new NullDumper();
407+
return $this->proxyDumper ??= new LazyServiceDumper($this->class);
405408
}
406409

407410
private function analyzeReferences()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\LazyProxy\Instantiator;
13+
14+
use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
15+
use Symfony\Component\DependencyInjection\ContainerInterface;
16+
use Symfony\Component\DependencyInjection\Definition;
17+
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
18+
use Symfony\Component\VarExporter\LazyGhostObjectInterface;
19+
use Symfony\Component\VarExporter\LazyGhostObjectTrait;
20+
21+
/**
22+
* @author Nicolas Grekas <[email protected]>
23+
*/
24+
final class LazyServiceInstantiator implements InstantiatorInterface
25+
{
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object
30+
{
31+
$dumper = new LazyServiceDumper();
32+
33+
if ($dumper->useProxyManager($definition)) {
34+
return (new RuntimeInstantiator())->instantiateProxy($container, $definition, $id, $realInstantiator);
35+
}
36+
37+
if (!class_exists($proxyClass = $dumper->getProxyClass($definition), false)) {
38+
eval(sprintf('class %s extends %s implements %s { use %s; }', $proxyClass, $definition->getClass(), LazyGhostObjectInterface::class, LazyGhostObjectTrait::class));
39+
}
40+
41+
return $proxyClass::createLazyGhostObject($realInstantiator);
42+
}
43+
}

src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/RealServiceInstantiator.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414
use Symfony\Component\DependencyInjection\ContainerInterface;
1515
use Symfony\Component\DependencyInjection\Definition;
1616

17+
trigger_deprecation('symfony/dependency-injection', '6.2', 'The "%s" class is deprecated, use "%s" instead.', RealServiceInstantiator::class, LazyServiceInstantiator::class);
18+
1719
/**
1820
* {@inheritdoc}
1921
*
2022
* Noop proxy instantiator - produces the real service instead of a proxy instance.
2123
*
2224
* @author Marco Pivetta <[email protected]>
25+
*
26+
* @deprecated since Symfony 6.2, use LazyServiceInstantiator instead.
2327
*/
2428
class RealServiceInstantiator implements InstantiatorInterface
2529
{
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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\LazyProxy\PhpDumper;
13+
14+
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
15+
use Symfony\Component\DependencyInjection\Definition;
16+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
17+
use Symfony\Component\DependencyInjection\Exception\LogicException;
18+
use Symfony\Component\VarExporter\LazyGhostObjectInterface;
19+
use Symfony\Component\VarExporter\LazyGhostObjectTrait;
20+
21+
/**
22+
* @author Nicolas Grekas <[email protected]>
23+
*/
24+
final class LazyServiceDumper implements DumperInterface
25+
{
26+
public function __construct(
27+
private string $salt = '',
28+
) {
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null): bool
35+
{
36+
$asGhostObject = false;
37+
38+
if ($definition->hasTag('proxy')) {
39+
if (!$definition->isLazy()) {
40+
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()));
41+
}
42+
43+
return true;
44+
}
45+
46+
if (!$definition->isLazy()) {
47+
return false;
48+
}
49+
50+
if (!($class = $definition->getClass()) || !(class_exists($class) || interface_exists($class, false))) {
51+
return false;
52+
}
53+
54+
$class = new \ReflectionClass($class);
55+
56+
if ($class->isFinal()) {
57+
throw new InvalidArgumentException(sprintf('Cannot make service of class "%s" lazy because the class is final.', $definition->getClass()));
58+
}
59+
60+
if ($asGhostObject = !$class->isAbstract() && !$class->isInterface() && ('stdClass' === $class->name || !$class->isInternal())) {
61+
while ($class = $class->getParentClass()) {
62+
if (!$asGhostObject = 'stdClass' === $class->name || !$class->isInternal()) {
63+
break;
64+
}
65+
}
66+
}
67+
68+
return true;
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string
75+
{
76+
if ($dumper = $this->useProxyManager($definition)) {
77+
return $dumper->getProxyFactoryCode($definition, $id, $factoryCode);
78+
}
79+
80+
$instantiation = 'return';
81+
82+
if ($definition->isShared()) {
83+
$instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true));
84+
}
85+
86+
$proxyClass = $this->getProxyClass($definition);
87+
88+
if (preg_match('/^\$this->\w++\(\$proxy\)$/', $factoryCode)) {
89+
$factoryCode = substr_replace($factoryCode, '(...)', -8);
90+
} else {
91+
$factoryCode = sprintf('function ($proxy) { return %s; }', $factoryCode);
92+
}
93+
94+
return <<<EOF
95+
if (true === \$lazyLoad) {
96+
$instantiation \$this->createProxy('$proxyClass', function () {
97+
return \\$proxyClass::createLazyGhostObject($factoryCode);
98+
});
99+
}
100+
101+
102+
EOF;
103+
}
104+
105+
/**
106+
* {@inheritdoc}
107+
*/
108+
public function getProxyCode(Definition $definition): string
109+
{
110+
if ($dumper = $this->useProxyManager($definition)) {
111+
return $dumper->getProxyCode($definition);
112+
}
113+
114+
$proxyClass = $this->getProxyClass($definition);
115+
116+
return sprintf(<<<EOF
117+
class %s extends \%s implements \%s
118+
{
119+
use \%s;
120+
}
121+
122+
EOF,
123+
$proxyClass,
124+
$definition->getClass(),
125+
LazyGhostObjectInterface::class,
126+
LazyGhostObjectTrait::class
127+
);
128+
}
129+
130+
public function getProxyClass(Definition $definition): string
131+
{
132+
$class = (new \ReflectionClass($definition->getClass()))->name;
133+
134+
return preg_replace('/^.*\\\\/', '', $class).'_'.substr(hash('sha256', $this->salt.'+'.$class), -7);
135+
}
136+
137+
public function useProxyManager(Definition $definition): ?ProxyDumper
138+
{
139+
if (!$this->isProxyCandidate($definition, $asGhostObject)) {
140+
throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service of class "%s".', $definition->getClass()));
141+
}
142+
143+
if ($asGhostObject) {
144+
return null;
145+
}
146+
147+
if (!class_exists(ProxyDumper::class)) {
148+
throw new LogicException('You cannot use virtual proxies for lazy services as the ProxyManager bridge is not installed. Try running "composer require symfony/proxy-manager-bridge".');
149+
}
150+
151+
return new ProxyDumper($this->salt);
152+
}
153+
}

src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
3939
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
4040
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
41+
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
4142
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
4243
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
4344
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@@ -369,9 +370,13 @@ public function testCreateService()
369370
$this->assertInstanceOf(\Bar\FooClass::class, $builder->get('foo2'), '->createService() replaces parameters in the file provided by the service definition');
370371
}
371372

373+
/**
374+
* @group legacy
375+
*/
372376
public function testCreateProxyWithRealServiceInstantiator()
373377
{
374378
$builder = new ContainerBuilder();
379+
$builder->setProxyInstantiator(new RealServiceInstantiator());
375380

376381
$builder->register('foo1', 'Bar\FooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php');
377382
$builder->getDefinition('foo1')->setLazy(true);

0 commit comments

Comments
 (0)