diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute.php index 4f596a2b9059..3967b9a43bd3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute.php @@ -87,11 +87,7 @@ protected static function getFoo2Service($container, $lazyLoad = true) class FooProxyCd8d23a extends \Symfony\Component\DependencyInjection\Tests\Compiler\Foo implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyProxyTrait; - - 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); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php index fcf66ad12157..72f5cc11e57e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php @@ -76,19 +76,11 @@ class objectProxy1fd6daa implements \Symfony\Component\DependencyInjection\Tests { 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; + return \ReflectionLazyObject::fromInstance($this)->initialize(); } } // 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); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php index 60add492ba1c..a589f8e50258 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php @@ -104,23 +104,15 @@ protected static function getFooService($container, $lazyLoad = true) class stdClassGhostAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyGhostTrait; - - 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); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); class stdClassProxyAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyProxyTrait; - - 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); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt index 488895d7c1b6..e3214a6dfdc3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt @@ -45,14 +45,10 @@ namespace Container%s; class FooLazyClassGhost%s extends \Bar\FooLazyClass implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyGhostTrait; - - 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); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); if (!\class_exists('FooLazyClassGhost%s', false)) { \class_alias(__NAMESPACE__.'\\FooLazyClassGhost%s', 'FooLazyClassGhost%s', false); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php index b03463295309..9c4c3960eeaf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php @@ -78,11 +78,7 @@ protected static function getFooService($container, $lazyLoad = true) class stdClassGhostAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyGhostTrait; - - 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); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_public.php index 7f870f886abc..ef5cfd755abb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_public.php @@ -69,11 +69,7 @@ protected static function getFooService($container, $lazyLoad = true) class FooLazyClassGhost82ad1a4 extends \Bar\FooLazyClass implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyGhostTrait; - - 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); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php index b2940c88569f..b6073d0e2191 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php @@ -74,13 +74,7 @@ protected static function getWitherService($container, $lazyLoad = true) class WitherProxy1991f2a extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyProxyTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = [ - 'foo' => [parent::class, 'foo', null], - ]; } // 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); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php index 0df7e0c98e27..8e256483d78d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php @@ -76,13 +76,7 @@ protected static function getWitherService($container, $lazyLoad = true) class WitherProxyE94fdba extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyProxyTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = [ - 'foo' => [parent::class, 'foo', null], - ]; } // 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); diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index e7bd9a152a00..762efc6c28a9 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -179,6 +179,10 @@ public static function castClass(\ReflectionClass $c, array $a, Stub $stub, bool 'constants' => 'getReflectionConstants', ]); + if ($c instanceof \ReflectionLazyObject) { + $c = new \ReflectionClass($c->name); + } + foreach ($c->getProperties() as $n) { $a[$prefix.'properties'][$n->name] = $n; } diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php index f6450ce7f0e7..80a6d18e931c 100644 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php +++ b/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php @@ -25,114 +25,5 @@ class LazyObjectRegistry */ public static array $classReflectors = []; - /** - * @var array> - */ - public static array $defaultProperties = []; - - /** - * @var array> - */ - public static array $classResetters = []; - - /** - * @var array - */ - public static array $classAccessors = []; - - /** - * @var array - */ - public static array $parentMethods = []; - - public static ?\Closure $noInitializerState = null; - - public static function getClassResetters($class) - { - $classProperties = []; - - if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) { - $propertyScopes = []; - } else { - $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); - } - - foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) { - $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; - - if ($k === $key && "\0$class\0lazyObjectState" !== $k) { - $classProperties[$readonlyScope ?? $scope][$name] = $key; - } - } - - $resetters = []; - foreach ($classProperties as $scope => $properties) { - $resetters[] = \Closure::bind(static function ($instance, $skippedProperties) use ($properties) { - foreach ($properties as $name => $key) { - if (!\array_key_exists($key, $skippedProperties)) { - unset($instance->$name); - } - } - }, null, $scope); - } - - return $resetters; - } - - public static function getClassAccessors($class) - { - return \Closure::bind(static fn () => [ - 'get' => static function &($instance, $name, $readonly) { - if (!$readonly) { - return $instance->$name; - } - $value = $instance->$name; - - return $value; - }, - 'set' => static function ($instance, $name, $value) { - $instance->$name = $value; - }, - 'isset' => static fn ($instance, $name) => isset($instance->$name), - 'unset' => static function ($instance, $name) { - unset($instance->$name); - }, - ], null, \Closure::class === $class ? null : $class)(); - } - - public static function getParentMethods($class) - { - $parent = get_parent_class($class); - $methods = []; - - foreach (['set', 'isset', 'unset', 'clone', 'serialize', 'unserialize', 'sleep', 'wakeup', 'destruct', 'get'] as $method) { - if (!$parent || !method_exists($parent, '__'.$method)) { - $methods[$method] = false; - } else { - $m = new \ReflectionMethod($parent, '__'.$method); - $methods[$method] = !$m->isAbstract() && !$m->isPrivate(); - } - } - - $methods['get'] = $methods['get'] ? ($m->returnsReference() ? 2 : 1) : 0; - - return $methods; - } - - public static function getScope($propertyScopes, $class, $property, $readonlyScope = null) - { - if (null === $readonlyScope && !isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) { - return null; - } - $frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]; - - if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) { - $scope = $frame['object']->class; - } - if (null === $readonlyScope && '*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { - return null; - } - - return $scope; - } + public static \WeakMap $initializers; } diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php deleted file mode 100644 index 5fc398e05895..000000000000 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter\Internal; - -use Symfony\Component\VarExporter\Hydrator as PublicHydrator; - -/** - * Keeps the state of lazy objects. - * - * As a micro-optimization, this class uses no type declarations. - * - * @internal - */ -class LazyObjectState -{ - public const STATUS_UNINITIALIZED_FULL = 1; - public const STATUS_UNINITIALIZED_PARTIAL = 2; - public const STATUS_INITIALIZED_FULL = 3; - public const STATUS_INITIALIZED_PARTIAL = 4; - - /** - * @var self::STATUS_* - */ - public int $status = self::STATUS_UNINITIALIZED_FULL; - - public object $realInstance; - - /** - * @param array $skippedProperties - */ - public function __construct( - public readonly \Closure $initializer, - public readonly array $skippedProperties = [], - ) { - } - - public function initialize($instance, $propertyName, $propertyScope) - { - if (self::STATUS_UNINITIALIZED_FULL !== $this->status) { - return $this->status; - } - - $this->status = self::STATUS_INITIALIZED_PARTIAL; - - try { - if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) { - PublicHydrator::hydrate($instance, $defaultProperties); - } - - ($this->initializer)($instance); - } catch (\Throwable $e) { - $this->status = self::STATUS_UNINITIALIZED_FULL; - $this->reset($instance); - - throw $e; - } - - return $this->status = self::STATUS_INITIALIZED_FULL; - } - - public function reset($instance): void - { - $class = $instance::class; - $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); - $skippedProperties = $this->skippedProperties; - $properties = (array) $instance; - - foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) { - $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; - - if ($k === $key && (null !== $readonlyScope || !\array_key_exists($k, $properties))) { - $skippedProperties[$k] = true; - } - } - - foreach (LazyObjectRegistry::$classResetters[$class] as $reset) { - $reset($instance, $skippedProperties); - } - - foreach ((array) $instance as $name => $value) { - if ("\0" !== ($name[0] ?? '') && !\array_key_exists($name, $skippedProperties)) { - unset($instance->$name); - } - } - - $this->status = self::STATUS_UNINITIALIZED_FULL; - } -} diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectTrait.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectTrait.php index 4a6f232af85a..100a69b428e8 100644 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectTrait.php +++ b/src/Symfony/Component/VarExporter/Internal/LazyObjectTrait.php @@ -11,24 +11,42 @@ namespace Symfony\Component\VarExporter\Internal; -use Symfony\Component\Serializer\Attribute\Ignore; - -if (\PHP_VERSION_ID >= 80300) { +/** + * @internal + */ +trait LazyObjectTrait +{ /** - * @internal + * Returns whether the object is initialized. + * + * @param $partial Whether partially initialized objects should be considered as initialized */ - trait LazyObjectTrait + public function isLazyObjectInitialized(bool $partial = false): bool { - #[Ignore] - private readonly LazyObjectState $lazyObjectState; + return !\ReflectionLazyObject::isLazyObject($this); } -} else { + /** - * @internal + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object */ - trait LazyObjectTrait + public function resetLazyObject(): bool { - #[Ignore] - private LazyObjectState $lazyObjectState; + if (\ReflectionLazyObject::isLazyObject($this)) { + return true; + } + + if (![$initializer, $strategy, $skippedProperties] = LazyObjectRegistry::$initializers[$this] ?? null) { + return false; + } + + $r = \ReflectionLazyObject::makeLazy($this, $initializer, $strategy); + + foreach ($skippedProperties as $class => $properties) { + foreach ($properties as $property) { + $r->skipProperty($property, $class); + } + } + + return true; } } diff --git a/src/Symfony/Component/VarExporter/LazyGhostTrait.php b/src/Symfony/Component/VarExporter/LazyGhostTrait.php index fa82cedc41f8..43dfd9f278a1 100644 --- a/src/Symfony/Component/VarExporter/LazyGhostTrait.php +++ b/src/Symfony/Component/VarExporter/LazyGhostTrait.php @@ -11,10 +11,7 @@ namespace Symfony\Component\VarExporter; -use Symfony\Component\Serializer\Attribute\Ignore; -use Symfony\Component\VarExporter\Internal\Hydrator; -use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; -use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry; use Symfony\Component\VarExporter\Internal\LazyObjectTrait; trait LazyGhostTrait @@ -34,45 +31,33 @@ trait LazyGhostTrait */ public static function createLazyGhost(\Closure $initializer, ?array $skippedProperties = null, ?object $instance = null): static { - if (self::class !== $class = $instance ? $instance::class : static::class) { - $skippedProperties["\0".self::class."\0lazyObjectState"] = true; - } + $instance ??= (new \ReflectionClass(static::class))->newInstanceWithoutConstructor(); - if (!isset(Registry::$defaultProperties[$class])) { - Registry::$classReflectors[$class] ??= new \ReflectionClass($class); - $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); - Registry::$defaultProperties[$class] ??= (array) $instance; - Registry::$classResetters[$class] ??= Registry::getClassResetters($class); + $propertiesToSkip = []; + $wrapperInitializer = function ($instance) use ($initializer, &$wrapperInitializer, &$propertiesToSkip) { + $initializersRegistry = LazyObjectRegistry::$initializers ??= new \WeakMap(); + $initializersRegistry[$instance] = [$wrapperInitializer, \ReflectionLazyObject::STRATEGY_GHOST, $propertiesToSkip]; - if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { - Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; - } - } else { - $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); - } + $initializer($instance); + }; - $instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []); + $r = \ReflectionLazyObject::makeLazy($instance, $wrapperInitializer); - foreach (Registry::$classResetters[$class] as $reset) { - $reset($instance, $skippedProperties); - } + foreach ($skippedProperties ?? [] as $property => $v) { + if ("\0" === $property[0]) { + [, $class, $property] = explode("\0", $property, 3); - return $instance; - } - - /** - * Returns whether the object is initialized. - * - * @param $partial Whether partially initialized objects should be considered as initialized - */ - #[Ignore] - public function isLazyObjectInitialized(bool $partial = false): bool - { - if (!$state = $this->lazyObjectState ?? null) { - return true; + if ('*' === $class) { + $class = null; + } + } else { + $class = null; + } + $propertiesToSkip[$class][] = $property; + $r->skipProperty($property, $class); } - return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status; + return $instance; } /** @@ -80,273 +65,6 @@ public function isLazyObjectInitialized(bool $partial = false): bool */ public function initializeLazyObject(): static { - if (!$state = $this->lazyObjectState ?? null) { - return $this; - } - - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, '', null); - } - - return $this; - } - - /** - * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object - */ - public function resetLazyObject(): bool - { - if (!$state = $this->lazyObjectState ?? null) { - return false; - } - - if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $state->status) { - $state->reset($this); - } - - return true; - } - - public function &__get($name): mixed - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - - if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScope($propertyScopes, $class, $name); - $state = $this->lazyObjectState ?? null; - - if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) { - if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) { - // Work around php/php-src#12695 - $property = null === $scope ? $name : "\0$scope\0$name"; - $property = $propertyScopes[$property][3] - ?? Hydrator::$propertyScopes[$this::class][$property][3] = new \ReflectionProperty($scope ?? $class, $name); - } else { - $property = null; - } - - if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope)) { - goto get_in_scope; - } - } - } - - if ($parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']) { - if (2 === $parent) { - return parent::__get($name); - } - $value = parent::__get($name); - - return $value; - } - - if (null === $class) { - $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; - trigger_error(sprintf('Undefined property: %s::$%s in %s on line %s', $this::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); - } - - get_in_scope: - - try { - if (null === $scope) { - if (null === $readonlyScope) { - return $this->$name; - } - $value = $this->$name; - - return $value; - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['get']($this, $name, null !== $readonlyScope); - } catch (\Error $e) { - if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { - throw $e; - } - - try { - if (null === $scope) { - $this->$name = []; - - return $this->$name; - } - - $accessor['set']($this, $name, []); - - return $accessor['get']($this, $name, null !== $readonlyScope); - } catch (\Error) { - if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) { - throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious()); - } - - throw $e; - } - } - } - - public function __set($name, $value): void - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - - if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); - $state = $this->lazyObjectState ?? null; - - if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status - ) { - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, $name, $readonlyScope ?? $scope); - } - goto set_in_scope; - } - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { - parent::__set($name, $value); - - return; - } - - set_in_scope: - - if (null === $scope) { - $this->$name = $value; - } else { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['set']($this, $name, $value); - } - } - - public function __isset($name): bool - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - - if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScope($propertyScopes, $class, $name); - $state = $this->lazyObjectState ?? null; - - if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status - && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope) - ) { - goto isset_in_scope; - } - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { - return parent::__isset($name); - } - - isset_in_scope: - - if (null === $scope) { - return isset($this->$name); - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['isset']($this, $name); - } - - public function __unset($name): void - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - - if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); - $state = $this->lazyObjectState ?? null; - - if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status - ) { - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, $name, $readonlyScope ?? $scope); - } - goto unset_in_scope; - } - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { - parent::__unset($name); - - return; - } - - unset_in_scope: - - if (null === $scope) { - unset($this->$name); - } else { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['unset']($this, $name); - } - } - - public function __clone(): void - { - if ($state = $this->lazyObjectState ?? null) { - $this->lazyObjectState = clone $state; - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { - parent::__clone(); - } - } - - public function __serialize(): array - { - $class = self::class; - - if ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { - $properties = parent::__serialize(); - } else { - $this->initializeLazyObject(); - $properties = (array) $this; - } - unset($properties["\0$class\0lazyObjectState"]); - - if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { - return $properties; - } - - $scope = get_parent_class($class); - $data = []; - - foreach (parent::__sleep() as $name) { - $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; - - if (null === $k) { - trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); - } else { - $data[$k] = $value; - } - } - - return $data; - } - - public function __destruct() - { - $state = $this->lazyObjectState ?? null; - - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state?->status) { - return; - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { - parent::__destruct(); - } - } - - #[Ignore] - private function setLazyObjectAsInitialized(bool $initialized): void - { - if ($state = $this->lazyObjectState ?? null) { - $state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL; - } + return \ReflectionLazyObject::fromInstance($this)->initialize(); } } diff --git a/src/Symfony/Component/VarExporter/LazyProxyTrait.php b/src/Symfony/Component/VarExporter/LazyProxyTrait.php index 17ba1db985b9..1150bd860cfc 100644 --- a/src/Symfony/Component/VarExporter/LazyProxyTrait.php +++ b/src/Symfony/Component/VarExporter/LazyProxyTrait.php @@ -11,11 +11,7 @@ namespace Symfony\Component\VarExporter; -use Symfony\Component\Serializer\Attribute\Ignore; -use Symfony\Component\VarExporter\Hydrator as PublicHydrator; -use Symfony\Component\VarExporter\Internal\Hydrator; -use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; -use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry; use Symfony\Component\VarExporter\Internal\LazyObjectTrait; trait LazyProxyTrait @@ -30,330 +26,26 @@ trait LazyProxyTrait */ public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static { - if (self::class !== $class = $instance ? $instance::class : static::class) { - $skippedProperties = ["\0".self::class."\0lazyObjectState" => true]; - } + $instance ??= (new \ReflectionClass(static::class))->newInstanceWithoutConstructor(); - if (!isset(Registry::$defaultProperties[$class])) { - Registry::$classReflectors[$class] ??= new \ReflectionClass($class); - $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); - Registry::$defaultProperties[$class] ??= (array) $instance; - Registry::$classResetters[$class] ??= Registry::getClassResetters($class); + $wrapperInitializer = function ($instance) use ($initializer, &$wrapperInitializer) { + $initializersRegistry = LazyObjectRegistry::$initializers ??= new \WeakMap(); + $initializersRegistry[$instance] = [$wrapperInitializer, \ReflectionLazyObject::STRATEGY_VIRTUAL, []]; - if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { - Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; - } - } else { - $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); - } + return $initializer($instance); + }; - $instance->lazyObjectState = new LazyObjectState($initializer); - - foreach (Registry::$classResetters[$class] as $reset) { - $reset($instance, $skippedProperties ??= []); - } + \ReflectionLazyObject::makeLazy($instance, $wrapperInitializer, \ReflectionLazyObject::STRATEGY_VIRTUAL); return $instance; } - /** - * Returns whether the object is initialized. - * - * @param $partial Whether partially initialized objects should be considered as initialized - */ - #[Ignore] - public function isLazyObjectInitialized(bool $partial = false): bool - { - return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer; - } - /** * Forces initialization of a lazy object and returns it. */ public function initializeLazyObject(): parent { - if ($state = $this->lazyObjectState ?? null) { - return $state->realInstance ??= ($state->initializer)(); - } - - return $this; - } - - /** - * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object - */ - public function resetLazyObject(): bool - { - if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) { - return false; - } - - unset($this->lazyObjectState->realInstance); - - return true; - } - - public function &__get($name): mixed - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $instance = $this; - - if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScope($propertyScopes, $class, $name); - - if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - $parent = 2; - goto get_in_scope; - } - } - $parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']; - - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } else { - if (2 === $parent) { - return parent::__get($name); - } - $value = parent::__get($name); - - return $value; - } - - if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) { - $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; - trigger_error(sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); - } - - get_in_scope: - - try { - if (null === $scope) { - if (null === $readonlyScope && 1 !== $parent) { - return $instance->$name; - } - $value = $instance->$name; - - return $value; - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); - } catch (\Error $e) { - if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { - throw $e; - } - - try { - if (null === $scope) { - $instance->$name = []; - - return $instance->$name; - } - - $accessor['set']($instance, $name, []); - - return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); - } catch (\Error) { - throw $e; - } - } - } - - public function __set($name, $value): void - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $instance = $this; - - if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); - - if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - goto set_in_scope; - } - } - - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { - parent::__set($name, $value); - - return; - } - - set_in_scope: - - if (null === $scope) { - $instance->$name = $value; - } else { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['set']($instance, $name, $value); - } - } - - public function __isset($name): bool - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $instance = $this; - - if ([$class] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScope($propertyScopes, $class, $name); - - if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - goto isset_in_scope; - } - } - - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { - return parent::__isset($name); - } - - isset_in_scope: - - if (null === $scope) { - return isset($instance->$name); - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['isset']($instance, $name); - } - - public function __unset($name): void - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $instance = $this; - - if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); - - if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - goto unset_in_scope; - } - } - - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { - parent::__unset($name); - - return; - } - - unset_in_scope: - - if (null === $scope) { - unset($instance->$name); - } else { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['unset']($instance, $name); - } - } - - public function __clone(): void - { - if (!isset($this->lazyObjectState)) { - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { - parent::__clone(); - } - - return; - } - - $this->lazyObjectState = clone $this->lazyObjectState; - - if (isset($this->lazyObjectState->realInstance)) { - $this->lazyObjectState->realInstance = clone $this->lazyObjectState->realInstance; - } - } - - public function __serialize(): array - { - $class = self::class; - $state = $this->lazyObjectState ?? null; - - if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { - $properties = parent::__serialize(); - } else { - $properties = (array) $this; - - if ($state) { - unset($properties["\0$class\0lazyObjectState"]); - $properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)(); - } - } - - if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { - return $properties; - } - - $scope = get_parent_class($class); - $data = []; - - foreach (parent::__sleep() as $name) { - $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; - - if (null === $k) { - trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); - } else { - $data[$k] = $value; - } - } - - return $data; + return \ReflectionLazyObject::fromInstance($this)->initialize(); } - public function __unserialize(array $data): void - { - $class = self::class; - - if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) { - unset($data["\0$class\0lazyObjectReal"]); - - foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { - $reset($this, $data); - } - - if ($data) { - PublicHydrator::hydrate($this, $data); - } - $this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.')); - $this->lazyObjectState->realInstance = $instance; - } elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) { - parent::__unserialize($data); - } else { - PublicHydrator::hydrate($this, $data); - - if (Registry::$parentMethods[$class]['wakeup']) { - parent::__wakeup(); - } - } - } - - public function __destruct() - { - if (isset($this->lazyObjectState)) { - return; - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { - parent::__destruct(); - } - } } diff --git a/src/Symfony/Component/VarExporter/ProxyHelper.php b/src/Symfony/Component/VarExporter/ProxyHelper.php index c5978ee06820..30354a41909c 100644 --- a/src/Symfony/Component/VarExporter/ProxyHelper.php +++ b/src/Symfony/Component/VarExporter/ProxyHelper.php @@ -28,7 +28,7 @@ final class ProxyHelper public static function generateLazyGhost(\ReflectionClass $class): string { if (\PHP_VERSION_ID < 80300 && $class->isReadOnly()) { - throw new LogicException(sprintf('Cannot generate lazy ghost with PHP < 8.3: class "%s" is readonly.', $class->name)); + throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is readonly.', $class->name)); } if ($class->isFinal()) { throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name)); @@ -58,20 +58,15 @@ public static function generateLazyGhost(\ReflectionClass $class): string throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name)); } } - $propertyScopes = self::exportPropertyScopes($class->name); return <<name} implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyGhostTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes}; } // 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); EOPHP; } @@ -92,7 +87,7 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name)); } if (\PHP_VERSION_ID < 80300 && $class?->isReadOnly()) { - throw new LogicException(sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name)); + throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is readonly.', $class->name)); } $methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []]; @@ -110,30 +105,14 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf $extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal(); } while (!$extendsInternalClass && $parent = $parent->getParentClass()); } - $methodsHaveToBeProxied = $extendsInternalClass; $methods = []; - foreach ($methodReflectors as $method) { - if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) { - continue; - } - $methodsHaveToBeProxied = true; - $trait = new \ReflectionMethod(LazyProxyTrait::class, '__get'); - $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); - $body[0] = str_replace('): mixed', '): '.$type, $body[0]); - $methods['__get'] = strtr(implode('', $body).' }', [ - 'Hydrator' => '\\'.Hydrator::class, - 'Registry' => '\\'.LazyObjectRegistry::class, - ]); - break; - } - foreach ($methodReflectors as $method) { if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) { continue; } if ($method->isFinal()) { - if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) { + if ($extendsInternalClass || method_exists(LazyProxyTrait::class, $method->name)) { throw new LogicException(sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name)); } continue; @@ -149,14 +128,14 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf $body = " $parentCall;"; } elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) { $body = <<lazyObjectState)) { - (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); + if (\$this !== \$parent = \ReflectionLazyObject::fromInstance(\$this)->initialize()) { + \$parent->{$method->name}({$args}); } else { {$parentCall}; } EOPHP; } else { - if (!$methodsHaveToBeProxied && !$method->isAbstract()) { + if (!$extendsInternalClass && !$method->isAbstract()) { // Skip proxying methods that might return $this foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) { if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) { @@ -171,8 +150,8 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf } $body = <<lazyObjectState)) { - return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); + if (\$this !== \$parent = \ReflectionLazyObject::fromInstance(\$this)->initialize()) { + return \$parent->{$method->name}({$args}); } return {$parentCall}; @@ -195,20 +174,15 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf $methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods; } $body = $methods ? "\n".implode("\n\n", $methods)."\n" : ''; - $propertyScopes = $class ? self::exportPropertyScopes($class->name) : '[]'; return << $v) { - unset($propertyScopes[$k][3]); - } - $propertyScopes = VarExporter::export($propertyScopes); - $propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes); - $propertyScopes = preg_replace("/(?|(,)\n( ) |\n |,\n (\]))/", '$1$2', $propertyScopes); - $propertyScopes = str_replace("\n", "\n ", $propertyScopes); - - return $propertyScopes; - } - private static function exportDefault(\ReflectionParameter $param, $namespace): string { $default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2)); diff --git a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php index c5291238a2da..ef233f067e94 100644 --- a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php +++ b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php @@ -34,7 +34,6 @@ public function testGetPublic() $ghost->__construct(); }); - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); $this->assertSame(-4, $instance->public); $this->assertSame(4, $instance->publicReadonly); } @@ -56,7 +55,6 @@ public function testIssetPublic() $ghost->__construct(); }); - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); $this->assertTrue(isset($instance->public)); $this->assertSame(4, $instance->publicReadonly); } @@ -67,7 +65,6 @@ public function testUnsetPublic() $ghost->__construct(); }); - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); unset($instance->public); $this->assertSame(4, $instance->publicReadonly); $this->expectException(\BadMethodCallException::class); @@ -81,7 +78,6 @@ public function testSetPublic() $ghost->__construct(); }); - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); $instance->public = 12; $this->assertSame(12, $instance->public); $this->assertSame(4, $instance->publicReadonly); @@ -107,9 +103,7 @@ public function testClone() $clone = clone $instance; - $this->assertNotSame((array) $instance, (array) $clone); - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $clone)); + $this->assertSame((array) $instance, (array) $clone); $clone = clone $clone; $this->assertTrue($clone->resetLazyObject()); @@ -126,7 +120,6 @@ public function testSerialize() $clone = unserialize($serialized); $expected = (array) $instance; - $this->assertArrayHasKey("\0".TestClass::class."\0lazyObjectState", $expected); unset($expected["\0".TestClass::class."\0lazyObjectState"]); $this->assertSame(array_keys($expected), array_keys((array) $clone)); $this->assertFalse($clone->resetLazyObject()); @@ -209,7 +202,7 @@ public function testSetStdClassProperty() public function testLazyClass() { - $obj = new LazyClass(fn ($proxy) => $proxy->public = 123); + $obj = new LazyClass(function ($proxy) { $proxy->public = 123; }); $this->assertSame(123, $obj->public); } diff --git a/src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php b/src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php index c97bd75efdf4..f5f9d3b95859 100644 --- a/src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php +++ b/src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php @@ -68,12 +68,10 @@ public function testGenerateLazyProxy() { use \Symfony\Component\VarExporter\LazyProxyTrait; - private const LAZY_OBJECT_PROPERTY_SCOPES = []; - public function foo1(): ?\Symfony\Component\VarExporter\Tests\Bar { - if (isset($this->lazyObjectState)) { - return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo1(...\func_get_args()); + if ($this !== $parent = \ReflectionLazyObject::fromInstance($this)->initialize()) { + return $parent->foo1(...\func_get_args()); } return parent::foo1(...\func_get_args()); @@ -81,8 +79,8 @@ public function foo1(): ?\Symfony\Component\VarExporter\Tests\Bar public function foo4(\Symfony\Component\VarExporter\Tests\Bar|string $b, &$d): void { - if (isset($this->lazyObjectState)) { - ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo4($b, $d, ...\array_slice(\func_get_args(), 2)); + if ($this !== $parent = \ReflectionLazyObject::fromInstance($this)->initialize()) { + $parent->foo4($b, $d, ...\array_slice(\func_get_args(), 2)); } else { parent::foo4($b, $d, ...\array_slice(\func_get_args(), 2)); } @@ -90,8 +88,8 @@ public function foo4(\Symfony\Component\VarExporter\Tests\Bar|string $b, &$d): v protected function foo7() { - if (isset($this->lazyObjectState)) { - return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo7(...\func_get_args()); + if ($this !== $parent = \ReflectionLazyObject::fromInstance($this)->initialize()) { + return $parent->foo7(...\func_get_args()); } return throw new \BadMethodCallException('Cannot forward abstract method "Symfony\Component\VarExporter\Tests\TestForProxyHelper::foo7()".'); @@ -99,9 +97,7 @@ protected function foo7() } // 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); EOPHP; @@ -115,21 +111,15 @@ public function testGenerateLazyProxyForInterfaces() { use \Symfony\Component\VarExporter\LazyProxyTrait; - private const LAZY_OBJECT_PROPERTY_SCOPES = []; - public function initializeLazyObject(): \Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface1&\Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface2 { - if ($state = $this->lazyObjectState ?? null) { - return $state->realInstance ??= ($state->initializer)(); - } - - return $this; + return \ReflectionLazyObject::fromInstance($this)->initialize(); } public function foo1(): ?\Symfony\Component\VarExporter\Tests\Bar { - if (isset($this->lazyObjectState)) { - return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo1(...\func_get_args()); + if ($this !== $parent = \ReflectionLazyObject::fromInstance($this)->initialize()) { + return $parent->foo1(...\func_get_args()); } return throw new \BadMethodCallException('Cannot forward abstract method "Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface1::foo1()".'); @@ -137,8 +127,8 @@ public function foo1(): ?\Symfony\Component\VarExporter\Tests\Bar public function foo2(?\Symfony\Component\VarExporter\Tests\Bar $b, ...$d): \Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface2 { - if (isset($this->lazyObjectState)) { - return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo2(...\func_get_args()); + if ($this !== $parent = \ReflectionLazyObject::fromInstance($this)->initialize()) { + return $parent->foo2(...\func_get_args()); } return throw new \BadMethodCallException('Cannot forward abstract method "Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface2::foo2()".'); @@ -151,9 +141,7 @@ public static function foo3(): string } // 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); EOPHP; @@ -166,8 +154,8 @@ public function testAttributes() public function foo(#[\SensitiveParameter] $a): int { - if (isset($this->lazyObjectState)) { - return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo(...\func_get_args()); + if ($this !== $parent = \ReflectionLazyObject::fromInstance($this)->initialize()) { + return $parent->foo(...\func_get_args()); } return parent::foo(...\func_get_args());