Skip to content

[VarExporter] Leverage native lazy objects #59890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions UPGRADE-7.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,10 @@ VarDumper

* Deprecate `ResourceCaster::castCurl()`, `ResourceCaster::castGd()` and `ResourceCaster::castOpensslX509()`
* Mark all casters as `@internal`

VarExporter
-----------

* Deprecate using `ProxyHelper::generateLazyProxy()` when native lazy proxies can be used - the method should be used to generate abstraction-based lazy decorators only
* Deprecate `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead
* Deprecate `ProxyHelper::generateLazyGhost()`, use native lazy objects instead
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ public function testRefreshInvalidUser()
$provider->refreshUser($user2);
}

/**
* @group legacy
*/
public function testSupportProxy()
{
$em = DoctrineTestHelper::createTestEntityManager();
Expand Down Expand Up @@ -202,6 +205,9 @@ public function testPasswordUpgrades()
$provider->upgradePassword($user, 'foobar');
}

/**
* @group legacy
*/
public function testRefreshedUserProxyIsLoaded()
{
$em = DoctrineTestHelper::createTestEntityManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ public static function provideUniquenessConstraints(): iterable
yield 'Named arguments' => [new UniqueEntity(message: 'myMessage', fields: ['name'], em: 'foo')];
}

/**
* @group legacy
*/
public function testValidateEntityWithPrivatePropertyAndProxyObject()
{
$entity = new SingleIntIdWithPrivateNameEntity(1, 'Foo');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,18 +187,28 @@ public function getProxyClass(Definition $definition, bool $asGhostObject, ?\Ref
$class = 'object' !== $definition->getClass() ? $definition->getClass() : 'stdClass';
$class = new \ReflectionClass($class);

if (\PHP_VERSION_ID >= 80400) {
if ($asGhostObject) {
return $class->name;
}
if (\PHP_VERSION_ID < 80400) {
return preg_replace('/^.*\\\\/', '', $definition->getClass())
.($asGhostObject ? 'Ghost' : 'Proxy')
.ucfirst(substr(hash('xxh128', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7));
}

if ($asGhostObject) {
return $class->name;
}

if (!$definition->hasTag('proxy') && !$class->isInterface()) {
$parent = $class;
do {
$extendsInternalClass = $parent->isInternal();
} while (!$extendsInternalClass && $parent = $parent->getParentClass());

if (!$definition->hasTag('proxy') && !$class->isInterface()) {
if (!$extendsInternalClass) {
return $class->name;
}
}

return preg_replace('/^.*\\\\/', '', $definition->getClass())
.($asGhostObject ? 'Ghost' : 'Proxy')
return preg_replace('/^.*\\\\/', '', $definition->getClass()).'Proxy'
.ucfirst(substr(hash('xxh128', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2036,7 +2036,11 @@ public function testLazyAutowireAttributeWithIntersection()

$dumper = new PhpDumper($container);

$this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_autowire_attribute_with_intersection.php', $dumper->dump());
if (\PHP_VERSION_ID >= 80400) {
$this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_autowire_attribute_with_intersection.php', $dumper->dump());
} else {
$this->assertStringEqualsFile(self::$fixturesPath.'/php/legacy_lazy_autowire_attribute_with_intersection.php', $dumper->dump());
}
}

public function testCallableAdapterConsumer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,16 @@ protected static function get_Lazy_Foo_QFdMZVKService($container, $lazyLoad = tr

class objectProxy1fd6daa implements \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface, \Symfony\Component\DependencyInjection\Tests\Compiler\IInterface, \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyProxyTrait;
use \Symfony\Component\VarExporter\Internal\LazyDecoratorTrait;

private const LAZY_OBJECT_PROPERTY_SCOPES = [];

public function initializeLazyObject(): \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface&\Symfony\Component\DependencyInjection\Tests\Compiler\IInterface
{
if ($state = $this->lazyObjectState ?? null) {
return $state->realInstance ??= ($state->initializer)();
}

return $this;
return $this->lazyObjectState->realInstance;
}
}

// Help opcache.preload discover always-needed symbols
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

/**
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
*/
class ProjectServiceContainer extends Container
{
protected $parameters = [];

public function __construct()
{
$this->services = $this->privates = [];
$this->methodMap = [
'foo' => 'getFooService',
];

$this->aliases = [];
}

public function compile(): void
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}

public function isCompiled(): bool
{
return true;
}

protected function createProxy($class, \Closure $factory)
{
return $factory();
}

/**
* Gets the public 'foo' shared autowired service.
*
* @return \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer
*/
protected static function getFooService($container)
{
$a = ($container->privates['.lazy.foo.qFdMZVK'] ?? self::get_Lazy_Foo_QFdMZVKService($container));

if (isset($container->services['foo'])) {
return $container->services['foo'];
}

return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a);
}

/**
* Gets the private '.lazy.foo.qFdMZVK' shared service.
*
* @return \object
*/
protected static function get_Lazy_Foo_QFdMZVKService($container, $lazyLoad = true)
{
if (true === $lazyLoad) {
return $container->privates['.lazy.foo.qFdMZVK'] = $container->createProxy('objectProxy1fd6daa', static fn () => \objectProxy1fd6daa::createLazyProxy(static fn () => self::get_Lazy_Foo_QFdMZVKService($container, false)));
}

return ($container->services['foo'] ?? self::getFooService($container));
}
}

class objectProxy1fd6daa implements \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface, \Symfony\Component\DependencyInjection\Tests\Compiler\IInterface, \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyProxyTrait;

private const LAZY_OBJECT_PROPERTY_SCOPES = [];

public function initializeLazyObject(): \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface&\Symfony\Component\DependencyInjection\Tests\Compiler\IInterface
{
if ($state = $this->lazyObjectState ?? null) {
return $state->realInstance ??= ($state->initializer)();
}

return $this;
}
}

// Help opcache.preload discover always-needed symbols
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected static function getBarService($container, $lazyLoad = true)
protected static function getBazService($container, $lazyLoad = true)
{
if (true === $lazyLoad) {
return $container->services['baz'] = new \ReflectionClass('stdClass')->newLazyProxy(static fn () => self::getBazService($container, false));
return $container->services['baz'] = $container->createProxy('stdClassProxyAa01f12', static fn () => \stdClassProxyAa01f12::createLazyProxy(static fn () => self::getBazService($container, false)));
}

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

return \foo_bar();
Expand All @@ -100,3 +100,14 @@ protected static function getFooService($container, $lazyLoad = true)
return $lazyLoad;
}
}

class stdClassProxyAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\Internal\LazyDecoratorTrait;

private const LAZY_OBJECT_PROPERTY_SCOPES = [];
}

// Help opcache.preload discover always-needed symbols
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Symfony\Component\HttpKernel\Tests\Fixtures;

class LazyResettableService
class LazyResettableService extends \stdClass
{
public static $counter = 0;

Expand Down
7 changes: 7 additions & 0 deletions src/Symfony/Component/VarExporter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
CHANGELOG
=========

7.3
---

* Deprecate using `ProxyHelper::generateLazyProxy()` when native lazy proxies can be used - the method should be used to generate abstraction-based lazy decorators only
* Deprecate `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead
* Deprecate `ProxyHelper::generateLazyGhost()`, use native lazy objects instead

7.2
---

Expand Down
Loading
Loading