Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6b9b1fd

Browse filesBrowse files
[VarExporter] Leverage native lazy objects
1 parent 18a5c70 commit 6b9b1fd
Copy full SHA for 6b9b1fd

31 files changed

+971
-225
lines changed

‎UPGRADE-7.3.md

Copy file name to clipboardExpand all lines: UPGRADE-7.3.md
+7
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php
+17-7
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+5-1
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php
+2-7
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php
+13-2
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/Fixtures/LazyResettableService.php
+1-1
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/VarExporter/CHANGELOG.md
+7
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

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 <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\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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.