Skip to content

Commit ed695a6

Browse files
[DependencyInjection] Leverage native lazy objects for lazy services
1 parent c95120d commit ed695a6

27 files changed

+1212
-187
lines changed

src/Symfony/Bridge/Doctrine/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+
7.3
5+
---
6+
7+
* Reset the manager registry using native lazy objects when applicable
8+
49
7.2
510
---
611

src/Symfony/Bridge/Doctrine/ManagerRegistry.php

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,60 @@ protected function resetService($name): void
4545

4646
return;
4747
}
48-
if (!$manager instanceof LazyLoadingInterface) {
49-
throw new \LogicException(\sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name));
48+
if (\PHP_VERSION_ID < 80400) {
49+
if (!$manager instanceof LazyLoadingInterface) {
50+
throw new \LogicException(\sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name));
51+
}
52+
trigger_deprecation('symfony/doctrine-bridge', '7.3', 'Support for proxy-manager is deprecated.');
53+
54+
if ($manager instanceof GhostObjectInterface) {
55+
throw new \LogicException('Resetting a lazy-ghost-object manager service is not supported.');
56+
}
57+
$manager->setProxyInitializer(\Closure::bind(
58+
function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) {
59+
$name = $this->aliases[$name] ?? $name;
60+
$wrappedInstance = match (true) {
61+
isset($this->fileMap[$name]) => $this->load($this->fileMap[$name], false),
62+
(new \ReflectionMethod($this, $method = $this->methodMap[$name]))->isStatic() => $this->{$method}($this, false),
63+
default => $this->{$method}(false),
64+
};
65+
$manager->setProxyInitializer(null);
66+
67+
return true;
68+
},
69+
$this->container,
70+
Container::class
71+
));
72+
73+
return;
5074
}
51-
if ($manager instanceof GhostObjectInterface) {
52-
throw new \LogicException('Resetting a lazy-ghost-object manager service is not supported.');
75+
76+
$r = new \ReflectionClass($manager);
77+
78+
if ($r->isUninitializedLazyObject($manager)) {
79+
return;
80+
}
81+
82+
try {
83+
$r->resetAsLazyProxy($manager, \Closure::bind(
84+
function () use ($name) {
85+
$name = $this->aliases[$name] ?? $name;
86+
87+
return match (true) {
88+
isset($this->fileMap[$name]) => $this->load($this->fileMap[$name], false),
89+
(new \ReflectionMethod($this, $method = $this->methodMap[$name]))->isStatic() => $this->{$method}($this, false),
90+
default => $this->{$method}(false),
91+
};
92+
},
93+
$this->container,
94+
Container::class
95+
));
96+
} catch (\Error $e) {
97+
if (__FILE__ !== $e->getFile()) {
98+
throw $e;
99+
}
100+
101+
throw new \LogicException(\sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name), 0, $e);
53102
}
54-
$manager->setProxyInitializer(\Closure::bind(
55-
function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) {
56-
if (isset($this->aliases[$name])) {
57-
$name = $this->aliases[$name];
58-
}
59-
if (isset($this->fileMap[$name])) {
60-
$wrappedInstance = $this->load($this->fileMap[$name], false);
61-
} elseif ((new \ReflectionMethod($this, $this->methodMap[$name]))->isStatic()) {
62-
$wrappedInstance = $this->{$this->methodMap[$name]}($this, false);
63-
} else {
64-
$wrappedInstance = $this->{$this->methodMap[$name]}(false);
65-
}
66-
67-
$manager->setProxyInitializer(null);
68-
69-
return true;
70-
},
71-
$this->container,
72-
Container::class
73-
));
74103
}
75104
}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Don't skip classes with private constructor when autodiscovering
1010
* Add `Definition::addExcludeTag()` and `ContainerBuilder::findExcludedServiceIds()`
1111
for auto-configuration of classes excluded from the service container
12+
* Leverage native lazy objects when possible for lazy services
1213

1314
7.2
1415
---

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,19 @@ public function instantiateProxy(ContainerInterface $container, Definition $defi
2929
throw new InvalidArgumentException(\sprintf('Cannot instantiate lazy proxy for service "%s".', $id));
3030
}
3131

32-
if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $asGhostObject), false)) {
32+
if (\PHP_VERSION_ID >= 80400 && $asGhostObject) {
33+
return (new \ReflectionClass($definition->getClass()))->newLazyGhost(static function ($ghost) use ($realInstantiator) { $realInstantiator($ghost); });
34+
}
35+
36+
$class = null;
37+
if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $asGhostObject, $class), false)) {
3338
eval($dumper->getProxyCode($definition, $id));
3439
}
3540

36-
return $asGhostObject ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator);
41+
if ($definition->getClass() === $proxyClass) {
42+
return $class->newLazyProxy($realInstantiator);
43+
}
44+
45+
return \PHP_VERSION_ID < 80400 && $asGhostObject ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator);
3746
}
3847
}

src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,21 @@ public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject =
5656
}
5757
}
5858

59+
if (\PHP_VERSION_ID < 80400) {
60+
try {
61+
$asGhostObject = (bool) ProxyHelper::generateLazyGhost(new \ReflectionClass($class));
62+
} catch (LogicException) {
63+
}
64+
65+
return true;
66+
}
67+
5968
try {
60-
$asGhostObject = (bool) ProxyHelper::generateLazyGhost(new \ReflectionClass($class));
61-
} catch (LogicException) {
69+
$asGhostObject = (bool) (new \ReflectionClass($class))->newLazyGhost(static fn () => null);
70+
} catch (\Error $e) {
71+
if (__FILE__ !== $e->getFile()) {
72+
throw $e;
73+
}
6274
}
6375

6476
return true;
@@ -76,6 +88,16 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $
7688
$proxyClass = $this->getProxyClass($definition, $asGhostObject);
7789

7890
if (!$asGhostObject) {
91+
if ($definition->getClass() === $proxyClass) {
92+
return <<<EOF
93+
if (true === \$lazyLoad) {
94+
$instantiation new \ReflectionClass('$proxyClass')->newLazyProxy(static fn () => $factoryCode);
95+
}
96+
97+
98+
EOF;
99+
}
100+
79101
return <<<EOF
80102
if (true === \$lazyLoad) {
81103
$instantiation \$container->createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyProxy(static fn () => $factoryCode));
@@ -85,11 +107,23 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $
85107
EOF;
86108
}
87109

88-
$factoryCode = \sprintf('static fn ($proxy) => %s', $factoryCode);
110+
if (\PHP_VERSION_ID < 80400) {
111+
$factoryCode = \sprintf('static fn ($proxy) => %s', $factoryCode);
112+
113+
return <<<EOF
114+
if (true === \$lazyLoad) {
115+
$instantiation \$container->createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyGhost($factoryCode));
116+
}
117+
118+
119+
EOF;
120+
}
121+
122+
$factoryCode = \sprintf('static function ($proxy) use ($container) { %s; }', $factoryCode);
89123

90124
return <<<EOF
91125
if (true === \$lazyLoad) {
92-
$instantiation \$container->createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyGhost($factoryCode));
126+
$instantiation new \ReflectionClass('$proxyClass')->newLazyGhost($factoryCode);
93127
}
94128
95129
@@ -104,12 +138,21 @@ public function getProxyCode(Definition $definition, ?string $id = null): string
104138
$proxyClass = $this->getProxyClass($definition, $asGhostObject, $class);
105139

106140
if ($asGhostObject) {
141+
if (\PHP_VERSION_ID >= 80400) {
142+
return '';
143+
}
144+
107145
try {
108146
return ($class?->isReadOnly() ? 'readonly ' : '').'class '.$proxyClass.ProxyHelper::generateLazyGhost($class);
109147
} catch (LogicException $e) {
110148
throw new InvalidArgumentException(\sprintf('Cannot generate lazy ghost for service "%s".', $id ?? $definition->getClass()), 0, $e);
111149
}
112150
}
151+
152+
if ($definition->getClass() === $proxyClass) {
153+
return '';
154+
}
155+
113156
$interfaces = [];
114157

115158
if ($definition->hasTag('proxy')) {
@@ -144,6 +187,16 @@ public function getProxyClass(Definition $definition, bool $asGhostObject, ?\Ref
144187
$class = 'object' !== $definition->getClass() ? $definition->getClass() : 'stdClass';
145188
$class = new \ReflectionClass($class);
146189

190+
if (\PHP_VERSION_ID >= 80400) {
191+
if ($asGhostObject) {
192+
return $class->name;
193+
}
194+
195+
if (!$definition->hasTag('proxy') && !$class->isInterface()) {
196+
return $class->name;
197+
}
198+
}
199+
147200
return preg_replace('/^.*\\\\/', '', $definition->getClass())
148201
.($asGhostObject ? 'Ghost' : 'Proxy')
149202
.ucfirst(substr(hash('xxh128', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7));

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1919,8 +1919,12 @@ public function testLazyWither()
19191919
$container->compile();
19201920

19211921
$wither = $container->get('wither');
1922+
if (\PHP_VERSION_ID >= 80400) {
1923+
$this->assertTrue((new \ReflectionClass($wither))->isUninitializedLazyObject($wither));
1924+
} else {
1925+
$this->assertTrue($wither->resetLazyObject());
1926+
}
19221927
$this->assertInstanceOf(Foo::class, $wither->foo);
1923-
$this->assertTrue($wither->resetLazyObject());
19241928
$this->assertInstanceOf(Wither::class, $wither->withFoo1($wither->foo));
19251929
}
19261930

0 commit comments

Comments
 (0)