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

Skip to content

Commit e382b2f

Browse files
[VarExporter] Leverage native lazy objects
1 parent 18a5c70 commit e382b2f

31 files changed

+972
-226
lines changed

UPGRADE-7.3.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,10 @@ VarDumper
196196

197197
* Deprecate `ResourceCaster::castCurl()`, `ResourceCaster::castGd()` and `ResourceCaster::castOpensslX509()`
198198
* Mark all casters as `@internal`
199+
200+
VarExporter
201+
-----------
202+
203+
* Deprecate using `ProxyHelper::generateLazyProxy()` when native lazy proxies can be used - the method should be used to generate abstraction-based lazy decorators only
204+
* Deprecate `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead
205+
* Deprecate `ProxyHelper::generateLazyGhost()`, use native lazy objects instead

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,28 @@ public function getProxyClass(Definition $definition, bool $asGhostObject, ?\Ref
187187
$class = 'object' !== $definition->getClass() ? $definition->getClass() : 'stdClass';
188188
$class = new \ReflectionClass($class);
189189

190-
if (\PHP_VERSION_ID >= 80400) {
191-
if ($asGhostObject) {
192-
return $class->name;
193-
}
190+
if (\PHP_VERSION_ID < 80400) {
191+
return preg_replace('/^.*\\\\/', '', $definition->getClass())
192+
.($asGhostObject ? 'Ghost' : 'Proxy')
193+
.ucfirst(substr(hash('xxh128', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7));
194+
}
195+
196+
if ($asGhostObject) {
197+
return $class->name;
198+
}
199+
200+
if (!$definition->hasTag('proxy') && !$class->isInterface()) {
201+
$parent = $class;
202+
do {
203+
$extendsInternalClass = $parent->isInternal();
204+
} while (!$extendsInternalClass && $parent = $parent->getParentClass());
194205

195-
if (!$definition->hasTag('proxy') && !$class->isInterface()) {
206+
if (!$extendsInternalClass) {
196207
return $class->name;
197208
}
198209
}
199210

200-
return preg_replace('/^.*\\\\/', '', $definition->getClass())
201-
.($asGhostObject ? 'Ghost' : 'Proxy')
211+
return preg_replace('/^.*\\\\/', '', $definition->getClass()).'Proxy'
202212
.ucfirst(substr(hash('xxh128', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7));
203213
}
204214
}

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2036,7 +2036,11 @@ public function testLazyAutowireAttributeWithIntersection()
20362036

20372037
$dumper = new PhpDumper($container);
20382038

2039-
$this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_autowire_attribute_with_intersection.php', $dumper->dump());
2039+
if (\PHP_VERSION_ID >= 80400) {
2040+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_autowire_attribute_with_intersection.php', $dumper->dump());
2041+
} else {
2042+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/legacy_lazy_autowire_attribute_with_intersection.php', $dumper->dump());
2043+
}
20402044
}
20412045

20422046
public function testCallableAdapterConsumer()

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,21 +74,16 @@ protected static function get_Lazy_Foo_QFdMZVKService($container, $lazyLoad = tr
7474

7575
class objectProxy1fd6daa implements \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface, \Symfony\Component\DependencyInjection\Tests\Compiler\IInterface, \Symfony\Component\VarExporter\LazyObjectInterface
7676
{
77-
use \Symfony\Component\VarExporter\LazyProxyTrait;
77+
use \Symfony\Component\VarExporter\Internal\LazyDecoratorTrait;
7878

7979
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
8080

8181
public function initializeLazyObject(): \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface&\Symfony\Component\DependencyInjection\Tests\Compiler\IInterface
8282
{
83-
if ($state = $this->lazyObjectState ?? null) {
84-
return $state->realInstance ??= ($state->initializer)();
85-
}
86-
87-
return $this;
83+
return $this->lazyObjectState->realInstance;
8884
}
8985
}
9086

9187
// Help opcache.preload discover always-needed symbols
9288
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
9389
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
94-
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\ContainerInterface;
5+
use Symfony\Component\DependencyInjection\Container;
6+
use Symfony\Component\DependencyInjection\Exception\LogicException;
7+
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
8+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
9+
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
10+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
11+
12+
/**
13+
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
14+
*/
15+
class ProjectServiceContainer extends Container
16+
{
17+
protected $parameters = [];
18+
19+
public function __construct()
20+
{
21+
$this->services = $this->privates = [];
22+
$this->methodMap = [
23+
'foo' => 'getFooService',
24+
];
25+
26+
$this->aliases = [];
27+
}
28+
29+
public function compile(): void
30+
{
31+
throw new LogicException('You cannot compile a dumped container that was already compiled.');
32+
}
33+
34+
public function isCompiled(): bool
35+
{
36+
return true;
37+
}
38+
39+
protected function createProxy($class, \Closure $factory)
40+
{
41+
return $factory();
42+
}
43+
44+
/**
45+
* Gets the public 'foo' shared autowired service.
46+
*
47+
* @return \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer
48+
*/
49+
protected static function getFooService($container)
50+
{
51+
$a = ($container->privates['.lazy.foo.qFdMZVK'] ?? self::get_Lazy_Foo_QFdMZVKService($container));
52+
53+
if (isset($container->services['foo'])) {
54+
return $container->services['foo'];
55+
}
56+
57+
return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a);
58+
}
59+
60+
/**
61+
* Gets the private '.lazy.foo.qFdMZVK' shared service.
62+
*
63+
* @return \object
64+
*/
65+
protected static function get_Lazy_Foo_QFdMZVKService($container, $lazyLoad = true)
66+
{
67+
if (true === $lazyLoad) {
68+
return $container->privates['.lazy.foo.qFdMZVK'] = $container->createProxy('objectProxy1fd6daa', static fn () => \objectProxy1fd6daa::createLazyProxy(static fn () => self::get_Lazy_Foo_QFdMZVKService($container, false)));
69+
}
70+
71+
return ($container->services['foo'] ?? self::getFooService($container));
72+
}
73+
}
74+
75+
class objectProxy1fd6daa implements \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface, \Symfony\Component\DependencyInjection\Tests\Compiler\IInterface, \Symfony\Component\VarExporter\LazyObjectInterface
76+
{
77+
use \Symfony\Component\VarExporter\LazyProxyTrait;
78+
79+
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
80+
81+
public function initializeLazyObject(): \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface&\Symfony\Component\DependencyInjection\Tests\Compiler\IInterface
82+
{
83+
if ($state = $this->lazyObjectState ?? null) {
84+
return $state->realInstance ??= ($state->initializer)();
85+
}
86+
87+
return $this;
88+
}
89+
}
90+
91+
// Help opcache.preload discover always-needed symbols
92+
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
93+
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
94+
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected static function getBarService($container, $lazyLoad = true)
6666
protected static function getBazService($container, $lazyLoad = true)
6767
{
6868
if (true === $lazyLoad) {
69-
return $container->services['baz'] = new \ReflectionClass('stdClass')->newLazyProxy(static fn () => self::getBazService($container, false));
69+
return $container->services['baz'] = $container->createProxy('stdClassProxyAa01f12', static fn () => \stdClassProxyAa01f12::createLazyProxy(static fn () => self::getBazService($container, false)));
7070
}
7171

7272
return \foo_bar();
@@ -80,7 +80,7 @@ protected static function getBazService($container, $lazyLoad = true)
8080
protected static function getBuzService($container, $lazyLoad = true)
8181
{
8282
if (true === $lazyLoad) {
83-
return $container->services['buz'] = new \ReflectionClass('stdClass')->newLazyProxy(static fn () => self::getBuzService($container, false));
83+
return $container->services['buz'] = $container->createProxy('stdClassProxyAa01f12', static fn () => \stdClassProxyAa01f12::createLazyProxy(static fn () => self::getBuzService($container, false)));
8484
}
8585

8686
return \foo_bar();
@@ -100,3 +100,14 @@ protected static function getFooService($container, $lazyLoad = true)
100100
return $lazyLoad;
101101
}
102102
}
103+
104+
class stdClassProxyAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface
105+
{
106+
use \Symfony\Component\VarExporter\Internal\LazyDecoratorTrait;
107+
108+
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
109+
}
110+
111+
// Help opcache.preload discover always-needed symbols
112+
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
113+
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);

src/Symfony/Component/HttpKernel/Tests/Fixtures/LazyResettableService.php

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

1212
namespace Symfony\Component\HttpKernel\Tests\Fixtures;
1313

14-
class LazyResettableService
14+
class LazyResettableService extends \stdClass
1515
{
1616
public static $counter = 0;
1717

src/Symfony/Component/VarExporter/CHANGELOG.md

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

4+
7.3
5+
---
6+
7+
* Deprecate using `ProxyHelper::generateLazyProxy()` when native lazy proxies can be used - the method should be used to generate abstraction-based lazy decorators only
8+
* Deprecate `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead
9+
* Deprecate `ProxyHelper::generateLazyGhost()`, use native lazy objects instead
10+
411
7.2
512
---
613

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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\VarExporter\Internal;
13+
14+
use Symfony\Component\Serializer\Attribute\Ignore;
15+
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
16+
17+
/**
18+
* @internal
19+
*/
20+
trait LazyDecoratorTrait
21+
{
22+
#[Ignore]
23+
private readonly LazyObjectState $lazyObjectState;
24+
25+
/**
26+
* Creates a lazy-loading decorator.
27+
*
28+
* @param \Closure():object $initializer Returns the proxied object
29+
* @param static|null $instance
30+
*/
31+
public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static
32+
{
33+
$class = $instance ? $instance::class : static::class;
34+
35+
if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
36+
Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
37+
}
38+
39+
$instance ??= (Registry::$classReflectors[$class] ??= ($r = new \ReflectionClass($class))->hasProperty('lazyObjectState')
40+
? $r
41+
: throw new \LogicException('Cannot create a lazy proxy for a non-decorator object.')
42+
)->newInstanceWithoutConstructor();
43+
44+
$state = $instance->lazyObjectState ??= new LazyObjectState();
45+
$state->initializer = null;
46+
unset($state->realInstance);
47+
48+
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
49+
$reset($instance, []);
50+
}
51+
$state->initializer = $initializer;
52+
53+
return $instance;
54+
}
55+
56+
public function __construct(...$args)
57+
{
58+
self::createLazyProxy(static fn () => new parent(...$args), $this);
59+
}
60+
61+
public function __destruct()
62+
{
63+
}
64+
65+
#[Ignore]
66+
public function isLazyObjectInitialized(bool $partial = false): bool
67+
{
68+
return isset($this->lazyObjectState->realInstance);
69+
}
70+
71+
public function initializeLazyObject(): parent
72+
{
73+
return $this->lazyObjectState->realInstance;
74+
}
75+
76+
public function resetLazyObject(): bool
77+
{
78+
if (!isset($this->lazyObjectState->initializer)) {
79+
return false;
80+
}
81+
unset($this->lazyObjectState->realInstance);
82+
83+
return true;
84+
}
85+
86+
public function &__get($name): mixed
87+
{
88+
$instance = $this->lazyObjectState->realInstance;
89+
$class = $this::class;
90+
91+
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
92+
$notByRef = 0;
93+
94+
if ([, , , $access] = $propertyScopes[$name] ?? null) {
95+
$notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF || ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET;
96+
}
97+
98+
if ($notByRef || 2 !== ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['get'] ?: 2)) {
99+
$value = $instance->$name;
100+
101+
return $value;
102+
}
103+
104+
try {
105+
return $instance->$name;
106+
} catch (\Error $e) {
107+
if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
108+
throw $e;
109+
}
110+
111+
try {
112+
$instance->$name = [];
113+
114+
return $instance->$name;
115+
} catch (\Error) {
116+
if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) {
117+
throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious());
118+
}
119+
120+
throw $e;
121+
}
122+
}
123+
}
124+
125+
public function __set($name, $value): void
126+
{
127+
$this->lazyObjectState->realInstance->$name = $value;
128+
}
129+
130+
public function __isset($name): bool
131+
{
132+
return isset($this->lazyObjectState->realInstance->$name);
133+
}
134+
135+
public function __unset($name): void
136+
{
137+
if ($this->lazyObjectState->initializer) {
138+
unset($this->lazyObjectState->realInstance->$name);
139+
}
140+
}
141+
142+
public function __serialize(): array
143+
{
144+
return [$this->lazyObjectState->realInstance];
145+
}
146+
147+
public function __unserialize($data): void
148+
{
149+
$this->lazyObjectState = new LazyObjectState();
150+
$this->lazyObjectState->realInstance = $data[0];
151+
}
152+
153+
public function __clone(): void
154+
{
155+
$this->lazyObjectState->realInstance; // initialize lazy object
156+
$this->lazyObjectState = clone $this->lazyObjectState;
157+
}
158+
}

0 commit comments

Comments
 (0)