8000 [DependencyInjection] Use WeakReference to break circular references … · symfony/symfony@a3b7fca · GitHub
[go: up one dir, main page]

Skip to content

Commit a3b7fca

Browse files
[DependencyInjection] Use WeakReference to break circular references in the container
1 parent 96993d9 commit a3b7fca

File tree

65 files changed

+1097
-843
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1097
-843
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,17 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $
5353
$instantiation = 'return';
5454

5555
if ($definition->isShared()) {
56-
$instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true));
56+
$instantiation .= sprintf(' $container->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true));
5757
}
5858

5959
$proxifiedClass = new \ReflectionClass($this->proxyGenerator->getProxifiedClass($definition));
6060
$proxyClass = $this->getProxyClassName($proxifiedClass->name);
6161

6262
return <<<EOF
6363
if (true === \$lazyLoad) {
64-
$instantiation \$this->createProxy('$proxyClass', function () {
65-
return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
64+
$instantiation \$container->createProxy('$proxyClass', static function () use (\$containerRef) {
65+
return \\$proxyClass::staticProxyConstructor(static function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$containerRef) {
66+
\$container = \$containerRef->get();
6667
\$wrappedInstance = $factoryCode;
6768
6869
\$proxy->setProxyInitializer(null);

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
use %a
44
class LazyServiceProjectServiceContainer extends Container
55
{%a
6-
protected function getFooService($lazyLoad = true)
6+
protected static function getFooService($container, $lazyLoad = true)
77
{
8+
$containerRef = $container->ref;
9+
810
if (true === $lazyLoad) {
9-
return $this->services['foo'] = $this->createProxy('stdClass_%s', function () {
10-
return %S\stdClass_%s(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
11-
$wrappedInstance = $this->getFooService(false);
11+
return $container->services['foo'] = $container->createProxy('stdClass_%s', static function () use ($containerRef) {
12+
return %S\stdClass_%s(static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($containerRef) {
13+
$container = $containerRef->get();
14+
$wrappedInstance = self::getFooService($containerRef->get(), false);
1215

1316
$proxy->setProxyInitializer(null);
1417

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77

88
public function getFooService($lazyLoad = true)
99
{
10+
$container = $this;
11+
$containerRef = \WeakReference::create($this);
12+
1013
if (true === $lazyLoad) {
11-
return $this->privates['foo'] = $this->createProxy('SunnyInterface_1eff735', function () {
12-
return \SunnyInterface_1eff735::staticProxyConstructor(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
13-
$wrappedInstance = $this->getFooService(false);
14+
return $container->privates['foo'] = $container->createProxy('SunnyInterface_1eff735', static function () use ($containerRef) {
15+
return \SunnyInterface_1eff735::staticProxyConstructor(static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($containerRef) {
16+
$container = $containerRef->get();
17+
$wrappedInstance = $container->getFooService(false);
1418

1519
$proxy->setProxyInitializer(null);
1620

src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ public function testGetProxyFactoryCode()
7272

7373
$definition->setLazy(true);
7474

75-
$code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)');
75+
$code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFoo2Service(false)');
7676

7777
$this->assertStringMatchesFormat(
78-
'%A$wrappedInstance = $this->getFoo2Service(false);%w$proxy->setProxyInitializer(null);%A',
78+
'%A$wrappedInstance = $container->getFoo2Service(false);%w$proxy->setProxyInitializer(null);%A',
7979
$code
8080
);
8181
}
@@ -87,9 +87,9 @@ public function testCorrectAssigning(Definition $definition, $access)
8787
{
8888
$definition->setLazy(true);
8989

90-
$code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)');
90+
$code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFoo2Service(false)');
9191

92-
$this->assertStringMatchesFormat('%A$this->'.$access.'[\'foo\'] = %A', $code);
92+
$this->assertStringMatchesFormat('%A$container->'.$access.'[\'foo\'] = %A', $code);
9393
}
9494

9595
public function getPrivatePublicDefinitions()
@@ -118,7 +118,7 @@ public function testGetProxyFactoryCodeForInterface()
118118
$definition->addTag('proxy', ['interface' => SunnyInterface::class]);
119119

120120
$implem = "<?php\n\n".$this->dumper->getProxyCode($definition);
121-
$factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFooService(false)');
121+
$factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFooService(false)');
122122
$factory = <<<EOPHP
123123
<?php
124124
@@ -129,6 +129,9 @@ public function testGetProxyFactoryCodeForInterface()
129129
130130
public function getFooService(\$lazyLoad = true)
131131
{
132+
\$container = \$this;
133+
\$containerRef = \\WeakReference::create(\$this);
134+
132135
{$factory} return new {$class}();
133136
}
134137

src/Symfony/Bridge/ProxyManager/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": ">=8.1",
2020
"friendsofphp/proxy-manager-lts": "^1.0.2",
21-
"symfony/dependency-injection": "^6.2",
21+
"symfony/dependency-injection": "^6.3",
2222
"symfony/deprecation-contracts": "^2.1|^3"
2323
},
2424
"require-dev": {

src/Symfony/Component/DependencyInjection/Container.php

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class Container implements ContainerInterface, ResetInterface
6363
private bool $compiled = false;
6464
private \Closure $getEnv;
6565

66+
private static $make;
67+
6668
public function __construct(ParameterBagInterface $parameterBag = null)
6769
{
6870
$this->parameterBag = $parameterBag ?? new EnvPlaceholderParameterBag();
@@ -135,7 +137,7 @@ public function set(string $id, ?object $service)
135137
if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) {
136138
$initialize = $this->privates['service_container'];
10000 137139
unset($this->privates['service_container']);
138-
$initialize();
140+
$initialize($this);
139141
}
140142

141143
if ('service_container' === $id) {
@@ -195,49 +197,49 @@ public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALI
195197
{
196198
return $this->services[$id]
197199
?? $this->services[$id = $this->aliases[$id] ?? $id]
198-
?? ('service_container' === $id ? $this : ($this->factories[$id] ?? $this->make(...))($id, $invalidBehavior));
200+
?? ('service_container' === $id ? $this : ($this->factories[$id] ?? self::$make ??= self::make(...))($this, $id, $invalidBehavior));
199201
}
200202

201203
/**
202204
* Creates a service.
203205
*
204206
* As a separate method to allow "get()" to use the really fast `??` operator.
205207
*/
206-
private function make(string $id, int $invalidBehavior)
208+
private static function make($container, string $id, int $invalidBehavior)
207209
{
208-
if (isset($this->loading[$id])) {
209-
throw new ServiceCircularReferenceException($id, array_merge(array_keys($this->loading), [$id]));
210+
if (isset($container->loading[$id])) {
211+
throw new ServiceCircularReferenceException($id, array_merge(array_keys($container->loading), [$id]));
210212
}
211213

212-
$this->loading[$id] = true;
214+
$container->loading[$id] = true;
213215

214216
try {
215-
if (isset($this->fileMap[$id])) {
216-
return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]);
217-
} elseif (isset($this->methodMap[$id])) {
218-
return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}();
217+
if (isset($container->fileMap[$id])) {
218+
return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $container->load($container->fileMap[$id]);
219+
} elseif (isset($container->methodMap[$id])) {
220+
return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $container->{$container->methodMap[$id]}($container);
219221
}
220222
} catch (\Exception $e) {
221-
unset($this->services[$id]);
223+
unset($container->services[$id]);
222224

223225
throw $e;
224226
} finally {
225-
unset($this->loading[$id]);
227+
unset($container->loading[$id]);
226228
}
227229

228230
if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
229231
if (!$id) {
230232
throw new ServiceNotFoundException($id);
231233
}
232-
if (isset($this->syntheticIds[$id])) {
234+
if (isset($container->syntheticIds[$id])) {
233235
throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is synthetic, it needs to be set at boot time before it can be used.', $id));
234236
}
235-
if (isset($this->getRemovedIds()[$id])) {
237+
if (isset($container->getRemovedIds()[$id])) {
236238
throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.', $id));
237239
}
238240

239241
$alternatives = [];
240-
foreach ($this->getServiceIds() as $knownId) {
242+
foreach ($container->getServiceIds() as $knownId) {
241243
if ('' === $knownId || '.' === $knownId[0]) {
242244
continue;
243245
}
@@ -378,13 +380,13 @@ final protected function getService(string|false $registry, string $id, ?string
378380
return false !== $registry ? $this->{$registry}[$id] ?? null : null;
379381
}
380382
if (false !== $registry) {
381-
return $this->{$registry}[$id] ??= $load ? $this->load($method) : $this->{$method}();
383+
return $this->{$registry}[$id] ??= $load ? $this->load($method) : $this->{$method}($this);
382384
}
383385
if (!$load) {
384-
return $this->{$method}();
386+
return $this->{$method}($this);
385387
}
386388

387-
return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory() : $this->load($method);
389+
return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory($this) : $this->load($method);
388390
}
389391

390392
private function __clone()

0 commit comments

Comments
 (0)
0