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 aaf3d4a

Browse filesBrowse files
[VarExporter] Leverage native lazy objects
1 parent 273f2ee commit aaf3d4a
Copy full SHA for aaf3d4a

16 files changed

+908
-192
lines changed

‎UPGRADE-7.3.md

Copy file name to clipboardExpand all lines: UPGRADE-7.3.md
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,9 @@ VarDumper
196196

197197
* Deprecate `ResourceCaster::castCurl()`, `ResourceCaster::castGd()` and `ResourceCaster::castOpensslX509()`
198198
* Mark all casters as `@internal`
199+
200+
VarExporter
201+
-----------
202+
203+
* Deprecate `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead
204+
* Deprecate `ProxyHelper::generateLazyGhost()`, use native lazy objects instead

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

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php
+4-6Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,15 @@ 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 {
78+
doCreateLazyProxy as public createLazyProxy;
79+
}
7880

7981
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
8082

8183
public function initializeLazyObject(): \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface&\Symfony\Component\DependencyInjection\Tests\Compiler\IInterface
8284
{
83-
if ($state = $this->lazyObjectState ?? null) {
84-
return $state->realInstance ??= ($state->initializer)();
85-
}
86-
87-
return $this;
85+
return $this->lazyObjectState->realInstance;
8886
}
8987
}
9088

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/VarExporter/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/VarExporter/CHANGELOG.md
+7Lines 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+
* Leverage native lazy objects in `ProxyHelper::generateLazyProxy()` on PHP 8.4+
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

+172Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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+
private static function doCreateLazyProxy(\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+
$r = Registry::$classReflectors[$class] ??= ($r = new \ReflectionClass($class))->hasProperty('lazyObjectState') ? $r : throw new \LogicException('Cannot create a lazy proxy for a non-decorator object.');
40+
$initializer = static function ($ghost) use ($initializer, $class) {
41+
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
42+
$reset($ghost, []);
43+
}
44+
45+
$state = $ghost->lazyObjectState ??= new LazyObjectState();
46+
$state->realInstance = $initializer();
47+
$state->initializer = $initializer;
48+
};
49+
50+
if (!$instance) {
51+
return $r->newLazyGhost($initializer);
52+
}
53+
54+
$r->resetAsLazyGhost($instance, $initializer);
55+
56+
return $instance;
57+
}
58+
59+
public function __construct(...$args)
60+
{
61+
self::createLazyProxy(static fn () => new parent(...$args), $this);
62+
}
63+
64+
public function __destruct()
65+
{
66+
}
67+
68+
/**
69+
* Returns whether the object is initialized.
70+
*
71+
* @param bool $partial Whether partially initialized objects should be considered as initialized
72+
*/
73+
#[Ignore]
74+
public function isLazyObjectInitialized(bool $partial = false): bool
75+
{
76+
$r = Registry::$classReflectors[static::class] ??= new \ReflectionClass(static::class);
77+
78+
return !$r->isUninitializedLazyObject($this);
79+
}
80+
81+
/**
82+
* Forces initialization of a lazy object and returns it.
83+
*/
84+
public function initializeLazyObject(): parent
85+
{
86+
return $this->lazyObjectState->realInstance;
87+
}
88+
89+
/**
90+
* @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
91+
*/
92+
public function resetLazyObject(): bool
93+
{
94+
$r = Registry::$classReflectors[static::class] ??= new \ReflectionClass(static::class);
95+
96+
if ($r->isUninitializedLazyObject($this)) {
97+
return true;
98+
}
99+
100+
return isset($this->lazyObjectState->initializer) && self::createLazyProxy($this->lazyObjectState->initializer, $this);
101+
}
102+
103+
public function &__get($name): mixed
104+
{
105+
$instance = $this->lazyObjectState->realInstance;
106+
$class = $this::class;
107+
108+
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
109+
$notByRef = 0;
110+
111+
if ([, , , $access] = $propertyScopes[$name] ?? null) {
112+
$notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF || ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET;
113+
}
114+
115+
if ($notByRef || 2 !== ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['get'] ?: 2)) {
116+
$value = $instance->$name;
117+
118+
return $value;
119+
}
120+
121+
try {
122+
return $instance->$name;
123+
} catch (\Error $e) {
124+
if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
125+
throw $e;
126+
}
127+
128+
try {
129+
$instance->$name = [];
130+
131+
return $instance->$name;
132+
} catch (\Error) {
133+
if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) {
134+
throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious());
135+
}
136+
137+
throw $e;
138+
}
139+
}
140+
}
141+
142+
public function __set($name, $value): void
143+
{
144+
$this->lazyObjectState->realInstance->$name = $value;
145+
}
146+
147+
public function __isset($name): bool
148+
{
149+
return isset($this->lazyObjectState->realInstance->$name);
150+
}
151+
152+
public function __unset($name): void
153+
{
154+
unset($this->lazyObjectState->realInstance->$name);
155+
}
156+
157+
public function __serialize(): array
158+
{
159+
return [$this->lazyObjectState->realInstance];
160+
}
161+
162+
public function __unserialize($data): void
163+
{
164+
$this->lazyObjectState = new LazyObjectState();
165+
$this->lazyObjectState->realInstance = $data[0];
166+
}
167+
168+
public function __clone(): void
169+
{
170+
$this->lazyObjectState = clone $this->lazyObjectState;
171+
}
172+
}

‎src/Symfony/Component/VarExporter/Internal/LazyObjectState.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/VarExporter/Internal/LazyObjectState.php
+15-1Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ class LazyObjectState
3333
public int $status = self::STATUS_UNINITIALIZED_FULL;
3434

3535
public object $realInstance;
36+
public object $cloneInstance;
3637

3738
/**
3839
* @param array<string, true> $skippedProperties
3940
*/
4041
public function __construct(
41-
public \Closure $initializer,
42+
public ?\Closure $initializer = null,
4243
public array $skippedProperties = [],
4344
) {
4445
}
@@ -94,4 +95,17 @@ public function reset($instance): void
9495

9596
$this->status = self::STATUS_UNINITIALIZED_FULL;
9697
}
98+
99+
public function __clone()
100+
{
101+
if (isset($this->cloneInstance)) {
102+
try {
103+
$this->realInstance = $this->cloneInstance;
104+
} finally {
105+
unset($this->cloneInstance);
106+
}
107+
} elseif (isset($this->realInstance)) {
108+
$this->realInstance = clone $this->realInstance;
109+
}
110+
}
97111
}

‎src/Symfony/Component/VarExporter/LazyGhostTrait.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/VarExporter/LazyGhostTrait.php
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
use Symfony\Component\VarExporter\Internal\LazyObjectState;
1818
use Symfony\Component\VarExporter\Internal\LazyObjectTrait;
1919

20+
if (\PHP_VERSION_ID >= 80400) {
21+
trigger_deprecation('symfony/var-exporter', '7.3', 'The "%s" trait is deprecated, use native lazy objects instead.', LazyGhostTrait::class);
22+
}
23+
24+
/**
25+
* @deprecated since Symfony 7.3, use native lazy objects instead
26+
*/
2027
trait LazyGhostTrait
2128
{
2229
use LazyObjectTrait;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.