diff --git a/UPGRADE-8.0.md b/UPGRADE-8.0.md index 8b9055d500f55..35723afe91ae0 100644 --- a/UPGRADE-8.0.md +++ b/UPGRADE-8.0.md @@ -16,3 +16,10 @@ TwigBridge ---------- * Remove `text` format from the `debug:twig` command, use the `txt` format instead + +VarExporter +----------- + + * Restrict `ProxyHelper::generateLazyProxy()` to generating abstraction-based lazy decorators; use native lazy proxies otherwise + * Remove `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead + * Remove `ProxyHelper::generateLazyGhost()`, use native lazy objects instead diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/json_streamer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/json_streamer.php index 4b38f0a506176..b6daf512c7937 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/json_streamer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/json_streamer.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use Symfony\Component\JsonStreamer\CacheWarmer\LazyGhostCacheWarmer; use Symfony\Component\JsonStreamer\CacheWarmer\StreamerCacheWarmer; use Symfony\Component\JsonStreamer\JsonStreamReader; use Symfony\Component\JsonStreamer\JsonStreamWriter; diff --git a/src/Symfony/Component/JsonStreamer/CacheWarmer/LazyGhostCacheWarmer.php b/src/Symfony/Component/JsonStreamer/CacheWarmer/LazyGhostCacheWarmer.php deleted file mode 100644 index 9160496142968..0000000000000 --- a/src/Symfony/Component/JsonStreamer/CacheWarmer/LazyGhostCacheWarmer.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\JsonStreamer\CacheWarmer; - -use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; -use Symfony\Component\JsonStreamer\Exception\RuntimeException; -use Symfony\Component\VarExporter\ProxyHelper; - -/** - * Generates lazy ghost {@see \Symfony\Component\VarExporter\LazyGhostTrait} - * PHP files for $streamable types. - * - * @author Mathias Arlaud - * - * @deprecated since Symfony 7.3, native lazy objects will be used instead - * - * @internal - */ -final class LazyGhostCacheWarmer extends CacheWarmer -{ - private Filesystem $fs; - - /** - * @param iterable $streamableClassNames - */ - public function __construct( - private iterable $streamableClassNames, - private string $lazyGhostsDir, - ) { - $this->fs = new Filesystem(); - } - - public function warmUp(string $cacheDir, ?string $buildDir = null): array - { - if (!$this->fs->exists($this->lazyGhostsDir)) { - $this->fs->mkdir($this->lazyGhostsDir); - } - - foreach ($this->streamableClassNames as $className) { - $this->warmClassLazyGhost($className); - } - - return []; - } - - public function isOptional(): bool - { - return true; - } - - /** - * @param class-string $className - */ - private function warmClassLazyGhost(string $className): void - { - $path = \sprintf('%s%s%s.php', $this->lazyGhostsDir, \DIRECTORY_SEPARATOR, hash('xxh128', $className)); - - try { - $classReflection = new \ReflectionClass($className); - } catch (\ReflectionException $e) { - throw new RuntimeException($e->getMessage(), $e->getCode(), $e); - } - - $this->writeCacheFile($path, \sprintf( - 'class %s%s', - \sprintf('%sGhost', preg_replace('/\\\\/', '', $className)), - ProxyHelper::generateLazyGhost($classReflection), - )); - } -} diff --git a/src/Symfony/Component/JsonStreamer/Tests/CacheWarmer/LazyGhostCacheWarmerTest.php b/src/Symfony/Component/JsonStreamer/Tests/CacheWarmer/LazyGhostCacheWarmerTest.php deleted file mode 100644 index fb10cc1c90d66..0000000000000 --- a/src/Symfony/Component/JsonStreamer/Tests/CacheWarmer/LazyGhostCacheWarmerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\JsonStreamer\Tests\CacheWarmer; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\JsonStreamer\CacheWarmer\LazyGhostCacheWarmer; -use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy; - -/** - * @group legacy - */ -class LazyGhostCacheWarmerTest extends TestCase -{ - private string $lazyGhostsDir; - - protected function setUp(): void - { - parent::setUp(); - - $this->lazyGhostsDir = \sprintf('%s/symfony_json_streamer_test/lazy_ghost', sys_get_temp_dir()); - - if (is_dir($this->lazyGhostsDir)) { - array_map('unlink', glob($this->lazyGhostsDir.'/*')); - rmdir($this->lazyGhostsDir); - } - } - - public function testWarmUpLazyGhost() - { - (new LazyGhostCacheWarmer([ClassicDummy::class], $this->lazyGhostsDir))->warmUp('useless'); - - $this->assertSame( - array_map(fn (string $c): string => \sprintf('%s/%s.php', $this->lazyGhostsDir, hash('xxh128', $c)), [ClassicDummy::class]), - glob($this->lazyGhostsDir.'/*'), - ); - } -} diff --git a/src/Symfony/Component/VarExporter/CHANGELOG.md b/src/Symfony/Component/VarExporter/CHANGELOG.md index 50b6d354f9727..32e4fde367bfe 100644 --- a/src/Symfony/Component/VarExporter/CHANGELOG.md +++ b/src/Symfony/Component/VarExporter/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +8.0 +--- + + * Restrict `ProxyHelper::generateLazyProxy()` to generating abstraction-based lazy decorators; use native lazy proxies otherwise + * Remove `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead + * Remove `ProxyHelper::generateLazyGhost()`, use native lazy objects instead + 7.3 --- diff --git a/src/Symfony/Component/VarExporter/Internal/LazyDecoratorTrait.php b/src/Symfony/Component/VarExporter/Internal/LazyDecoratorTrait.php index f05ca75d3b877..bd8e4d14c449d 100644 --- a/src/Symfony/Component/VarExporter/Internal/LazyDecoratorTrait.php +++ b/src/Symfony/Component/VarExporter/Internal/LazyDecoratorTrait.php @@ -95,7 +95,7 @@ public function &__get($name): mixed $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF || ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET; } - if ($notByRef || 2 !== ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['get'] ?: 2)) { + if ($notByRef || 2 !== ((Registry::$parentGet[$class] ??= Registry::getParentGet($class)) ?: 2)) { $value = $instance->$name; return $value; diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php index cb812cc092d7c..2d635e9b01ec6 100644 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php +++ b/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php @@ -41,11 +41,9 @@ class LazyObjectRegistry public static array $classAccessors = []; /** - * @var array + * @var array */ - public static array $parentMethods = []; - - public static ?\Closure $noInitializerState = null; + public static array $parentGet = []; public static function getClassResetters($class) { @@ -86,80 +84,16 @@ public static function getClassResetters($class) return $resetters; } - public static function getClassAccessors($class) - { - return \Closure::bind(static fn () => [ - 'get' => static function &($instance, $name, $notByRef) { - if (!$notByRef) { - 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) + public static function getParentGet($class): int { $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 getScopeForRead($propertyScopes, $class, $property) - { - if (!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 ('*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { - return null; - } - - return $scope; - } - - public static function getScopeForWrite($propertyScopes, $class, $property, $flags) - { - if (!($flags & (\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_READONLY | \ReflectionProperty::IS_PRIVATE_SET))) { - return null; + if (!$parent || !method_exists($parent, '__get')) { + return 0; } - $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 ($flags & (\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PRIVATE_SET)) { - return $scope; - } - if ($flags & (\ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PROTECTED_SET) && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { - return null; - } + $m = new \ReflectionMethod($parent, '__get'); - return $scope; + return !$m->isAbstract() && !$m->isPrivate() ? ($m->returnsReference() ? 2 : 1) : 0; } } diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php index 138aa749a6aaf..677a157ec836a 100644 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php +++ b/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php @@ -22,80 +22,10 @@ */ 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 ?\Closure $initializer = null; public object $realInstance; public object $cloneInstance; - /** - * @param array $skippedProperties - */ - public function __construct( - public ?\Closure $initializer = null, - public array $skippedProperties = [], - ) { - } - - public function initialize($instance, $propertyName, $writeScope) - { - 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, , $access]) { - $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; - - if ($k === $key && ($access & Hydrator::PROPERTY_HAS_HOOKS || ($access >> 2) & \ReflectionProperty::IS_READONLY || !\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; - } - public function __clone() { if (isset($this->cloneInstance)) { diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectTrait.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectTrait.php deleted file mode 100644 index bf1d989efc97f..0000000000000 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectTrait.php +++ /dev/null @@ -1,23 +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\Serializer\Attribute\Ignore; -/** - * @internal - * @deprecated since Symfony 7.3 - */ -trait LazyObjectTrait -{ - #[Ignore] - private readonly LazyObjectState $lazyObjectState; -} diff --git a/src/Symfony/Component/VarExporter/LazyGhostTrait.php b/src/Symfony/Component/VarExporter/LazyGhostTrait.php deleted file mode 100644 index 86e3e3f49bc7a..0000000000000 --- a/src/Symfony/Component/VarExporter/LazyGhostTrait.php +++ /dev/null @@ -1,374 +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; - -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\LazyObjectTrait; - -trigger_deprecation('symfony/var-exporter', '7.3', 'The "%s" trait is deprecated, use native lazy objects instead.', LazyGhostTrait::class); - -/** - * @deprecated since Symfony 7.3, use native lazy objects instead - */ -trait LazyGhostTrait -{ - use LazyObjectTrait; - - /** - * Creates a lazy-loading ghost instance. - * - * Skipped properties should be indexed by their array-cast identifier, see - * https://php.net/manual/language.types.array#language.types.array.casting - * - * @param \Closure(static):void $initializer The closure should initialize the object it receives as argument - * @param array|null $skippedProperties An array indexed by the properties to skip, a.k.a. the ones - * that the initializer doesn't initialize, if any - * @param static|null $instance - */ - 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; - } - - 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); - - if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { - Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; - } - } else { - $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); - } - - if (isset($instance->lazyObjectState)) { - $instance->lazyObjectState->initializer = $initializer; - $instance->lazyObjectState->skippedProperties = $skippedProperties ??= []; - - if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $instance->lazyObjectState->status) { - $instance->lazyObjectState->reset($instance); - } - - return $instance; - } - - $instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []); - - foreach (Registry::$classResetters[$class] as $reset) { - $reset($instance, $skippedProperties); - } - - return $instance; - } - - /** - * Returns whether the object is initialized. - * - * @param bool $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; - } - - return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status; - } - - /** - * Forces initialization of a lazy object and returns it. - */ - 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; - $notByRef = 0; - - if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForRead($propertyScopes, $class, $name); - $state = $this->lazyObjectState ?? null; - - if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) { - $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF; - - if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) { - // Work around php/php-src#12695 - $property = null === $scope ? $name : "\0$scope\0$name"; - $property = $propertyScopes[$property][4] - ?? Hydrator::$propertyScopes[$this::class][$property][4] = new \ReflectionProperty($scope ?? $class, $name); - } else { - $property = null; - } - if (!$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) { - $scope ??= $writeScope; - } - - if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $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 (!$notByRef) { - return $this->$name; - } - $value = $this->$name; - - return $value; - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['get']($this, $name, $notByRef); - } 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, $notByRef); - } 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, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); - $state = $this->lazyObjectState ?? null; - - if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status - ) { - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, $name, $writeScope ?? $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, , $writeScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForRead($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, $writeScope ?? $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, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); - $state = $this->lazyObjectState ?? null; - - if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status - ) { - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, $name, $writeScope ?? $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; - } - } -} diff --git a/src/Symfony/Component/VarExporter/LazyProxyTrait.php b/src/Symfony/Component/VarExporter/LazyProxyTrait.php deleted file mode 100644 index 5aacde7b1c18b..0000000000000 --- a/src/Symfony/Component/VarExporter/LazyProxyTrait.php +++ /dev/null @@ -1,374 +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; - -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\LazyObjectTrait; - -trigger_deprecation('symfony/var-exporter', '7.3', 'The "%s" trait is deprecated, use native lazy objects instead.', LazyProxyTrait::class); - -/** - * @deprecated since Symfony 7.3, use native lazy objects instead - */ -trait LazyProxyTrait -{ - use LazyObjectTrait; - - /** - * Creates a lazy-loading virtual proxy. - * - * @param \Closure():object $initializer Returns the proxied object - * @param static|null $instance - */ - 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]; - } - - if (!isset(Registry::$defaultProperties[$class])) { - Registry::$classReflectors[$class] ??= new \ReflectionClass($class); - $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); - Registry::$defaultProperties[$class] ??= (array) $instance; - - if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { - Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; - } - - Registry::$classResetters[$class] ??= Registry::getClassResetters($class); - } else { - $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); - } - - if (isset($instance->lazyObjectState)) { - $instance->lazyObjectState->initializer = $initializer; - unset($instance->lazyObjectState->realInstance); - - return $instance; - } - - $instance->lazyObjectState = new LazyObjectState($initializer); - - foreach (Registry::$classResetters[$class] as $reset) { - $reset($instance, $skippedProperties ??= []); - } - - return $instance; - } - - /** - * Returns whether the object is initialized. - * - * @param bool $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; - $notByRef = 0; - - if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF; - $scope = Registry::getScopeForRead($propertyScopes, $class, $name); - - if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - if (!$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) { - $scope ??= $writeScope; - } - $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: - $notByRef = $notByRef || 1 === $parent; - - try { - if (null === $scope) { - if (!$notByRef) { - return $instance->$name; - } - $value = $instance->$name; - - return $value; - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['get']($instance, $name, $notByRef); - } 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, $notByRef); - } 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, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); - - if ($writeScope === $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::getScopeForRead($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, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); - - if ($writeScope === $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; - } - - 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; - } - - 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 a52aba295cb0d..f6d1af3aa0618 100644 --- a/src/Symfony/Component/VarExporter/ProxyHelper.php +++ b/src/Symfony/Component/VarExporter/ProxyHelper.php @@ -21,102 +21,6 @@ */ final class ProxyHelper { - /** - * Helps generate lazy-loading ghost objects. - * - * @deprecated since Symfony 7.3, use native lazy objects instead - * - * @throws LogicException When the class is incompatible with ghost objects - */ - public static function generateLazyGhost(\ReflectionClass $class): string - { - if ($class->isFinal()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name)); - } - if ($class->isInterface() || $class->isAbstract() || $class->isTrait()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name)); - } - if (\stdClass::class !== $class->name && $class->isInternal()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name)); - } - if ($class->hasMethod('__get') && 'mixed' !== (self::exportType($class->getMethod('__get')) ?? 'mixed')) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name)); - } - - static $traitMethods; - $traitMethods ??= (new \ReflectionClass(LazyGhostTrait::class))->getMethods(); - - foreach ($traitMethods as $method) { - if ($class->hasMethod($method->name) && $class->getMethod($method->name)->isFinal()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: method "%s::%s()" is final.', $class->name, $method->name)); - } - } - - $parent = $class; - while ($parent = $parent->getParentClass()) { - if (\stdClass::class !== $parent->name && $parent->isInternal()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name)); - } - } - - $hooks = ''; - $propertyScopes = Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name); - foreach ($propertyScopes as $key => [$scope, $name, , $access]) { - $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; - $flags = $access >> 2; - - if ($k !== $key || !($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) { - continue; - } - - if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: property "%s::$%s" is final or private(set).', $class->name, $name)); - } - - $p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); - - $type = self::exportType($p); - $hooks .= "\n " - .($p->isProtected() ? 'protected' : 'public') - .($p->isProtectedSet() ? ' protected(set)' : '') - ." {$type} \${$name}" - .($p->hasDefaultValue() ? ' = '.VarExporter::export($p->getDefaultValue()) : '') - ." {\n"; - - foreach ($p->getHooks() as $hook => $method) { - if ('get' === $hook) { - $ref = ($method->returnsReference() ? '&' : ''); - $hooks .= " {$ref}get { \$this->initializeLazyObject(); return parent::\${$name}::get(); }\n"; - } elseif ('set' === $hook) { - $parameters = self::exportParameters($method, true); - $arg = '$'.$method->getParameters()[0]->name; - $hooks .= " set({$parameters}) { \$this->initializeLazyObject(); parent::\${$name}::set({$arg}); }\n"; - } else { - throw new LogicException(\sprintf('Cannot generate lazy ghost: hook "%s::%s()" is not supported.', $class->name, $method->name)); - } - } - - $hooks .= " }\n"; - } - - $propertyScopes = self::exportPropertyScopes($class->name, $propertyScopes); - - return <<name} implements \Symfony\Component\VarExporter\LazyObjectInterface - { - use \Symfony\Component\VarExporter\LazyGhostTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes}; - {$hooks}} - - // 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; - } - /** * Helps generate lazy-loading decorators. * @@ -140,8 +44,7 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf } while (!$extendsInternalClass && $parent = $parent->getParentClass()); if (!$extendsInternalClass) { - trigger_deprecation('symfony/var-exporter', '7.3', 'Generating lazy proxy for class "%s" is deprecated; leverage native lazy objects instead.', $class->name); - // throw new LogicException(\sprintf('Cannot generate lazy proxy: leverage native lazy objects instead for class "%s".', $class->name)); + throw new LogicException(\sprintf('Cannot generate lazy proxy: leverage native lazy objects instead for class "%s".', $class->name)); } } diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildMagicClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildMagicClass.php deleted file mode 100644 index 6cac9ffc03d01..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildMagicClass.php +++ /dev/null @@ -1,22 +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\Tests\Fixtures\LazyGhost; - -use Symfony\Component\VarExporter\LazyGhostTrait; -use Symfony\Component\VarExporter\LazyObjectInterface; - -class ChildMagicClass extends MagicClass implements LazyObjectInterface -{ - use LazyGhostTrait; - - private int $data = 123; -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildStdClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildStdClass.php deleted file mode 100644 index 265fb1d318c38..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildStdClass.php +++ /dev/null @@ -1,20 +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\Tests\Fixtures\LazyGhost; - -use Symfony\Component\VarExporter\LazyGhostTrait; -use Symfony\Component\VarExporter\LazyObjectInterface; - -class ChildStdClass extends \stdClass implements LazyObjectInterface -{ - use LazyGhostTrait; -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildTestClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildTestClass.php deleted file mode 100644 index ea5b8bfb26c3d..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildTestClass.php +++ /dev/null @@ -1,43 +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\Tests\Fixtures\LazyGhost; - -use Symfony\Component\VarExporter\LazyObjectInterface; - -class ChildTestClass extends TestClass implements LazyObjectInterface -{ - public int $public = 4; - public readonly int $publicReadonly; - protected int $protected = 5; - protected readonly int $protectedReadonly; - private int $private = 6; - - public function __construct() - { - if (6 !== $this->private) { - throw new \LogicException('Bad value for TestClass::$private'); - } - - $this->publicReadonly = 4; - $this->protectedReadonly = 5; - - parent::__construct(); - - if (-2 !== $this->protected) { - throw new \LogicException('Bad value for TestClass::$protected'); - } - - $this->public = -4; - $this->protected = -5; - $this->private = -6; - } -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ClassWithUninitializedObjectProperty.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ClassWithUninitializedObjectProperty.php deleted file mode 100644 index 352810c34ba1e..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ClassWithUninitializedObjectProperty.php +++ /dev/null @@ -1,17 +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\Tests\Fixtures\LazyGhost; - -class ClassWithUninitializedObjectProperty -{ - public \DateTimeInterface $property; -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/LazyClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/LazyClass.php deleted file mode 100644 index a3ce88d14e942..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/LazyClass.php +++ /dev/null @@ -1,28 +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\Tests\Fixtures\LazyGhost; - -use Symfony\Component\VarExporter\LazyGhostTrait; - -class LazyClass -{ - use LazyGhostTrait { - createLazyGhost as private; - } - - public int $public; - - public function __construct(\Closure $initializer) - { - self::createLazyGhost($initializer, [], $this); - } -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/MagicClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/MagicClass.php deleted file mode 100644 index 6ac1f7364b517..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/MagicClass.php +++ /dev/null @@ -1,59 +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\Tests\Fixtures\LazyGhost; - -class MagicClass -{ - public static int $destructCounter = 0; - public int $cloneCounter = 0; - private array $data = []; - - public function __construct() - { - $this->data['foo'] = 'bar'; - } - - public function __get($name) - { - return $this->data[$name] ?? null; - } - - public function __set($name, $value) - { - $this->data[$name] = $value; - } - - public function __isset($name): bool - { - return isset($this->data[$name]); - } - - public function __unset($name) - { - unset($this->data[$name]); - } - - public function __clone() - { - ++$this->cloneCounter; - } - - public function __sleep(): array - { - return ['data']; - } - - public function __destruct() - { - ++self::$destructCounter; - } -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/NoMagicClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/NoMagicClass.php deleted file mode 100644 index 88fa4f0dbd64b..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/NoMagicClass.php +++ /dev/null @@ -1,35 +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\Tests\Fixtures\LazyGhost; - -class NoMagicClass -{ - public function __get($name) - { - throw new \BadMethodCallException(__FUNCTION__."({$name})"); - } - - public function __set($name, $value) - { - throw new \BadMethodCallException(__FUNCTION__."({$name})"); - } - - public function __isset($name): bool - { - throw new \BadMethodCallException(__FUNCTION__."({$name})"); - } - - public function __unset($name) - { - throw new \BadMethodCallException(__FUNCTION__."({$name})"); - } -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ReadOnlyClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ReadOnlyClass.php deleted file mode 100644 index 28913220e78f4..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ReadOnlyClass.php +++ /dev/null @@ -1,20 +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\Tests\Fixtures\LazyGhost; - -readonly class ReadOnlyClass -{ - public function __construct( - public int $foo, - ) { - } -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/TestClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/TestClass.php deleted file mode 100644 index f755235831b1e..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/TestClass.php +++ /dev/null @@ -1,32 +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\Tests\Fixtures\LazyGhost; - -use Symfony\Component\VarExporter\LazyGhostTrait; - -class TestClass extends NoMagicClass -{ - use LazyGhostTrait; - - public int $public = 1; - protected int $protected = 2; - protected readonly int $protectedReadonly; - private int $private = 3; - - public function __construct() - { - $this->public = -1; - $this->protected = -2; - $this->protectedReadonly ??= 2; - $this->private = -3; - } -} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/RegularClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyProxy/RegularClass.php similarity index 84% rename from src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/RegularClass.php rename to src/Symfony/Component/VarExporter/Tests/Fixtures/LazyProxy/RegularClass.php index a81d57bee7929..5ed4c140f5222 100644 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/RegularClass.php +++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyProxy/RegularClass.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost; +namespace Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy; class RegularClass extends \stdClass { diff --git a/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php index 00866ad98e036..09313a9f8fa3a 100644 --- a/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php +++ b/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php @@ -17,13 +17,13 @@ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\VarExporter\Exception\LogicException; use Symfony\Component\VarExporter\ProxyHelper; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\RegularClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\AbstractHooked; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\AsymmetricVisibility; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\ConcreteReadOnlyClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\FinalPublicClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\Hooked; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\ReadOnlyClass; +use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\RegularClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\StringMagicGetClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\TestClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\TestOverwritePropClass; diff --git a/src/Symfony/Component/VarExporter/Tests/LegacyLazyGhostTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LegacyLazyGhostTraitTest.php deleted file mode 100644 index 1683a3c799834..0000000000000 --- a/src/Symfony/Component/VarExporter/Tests/LegacyLazyGhostTraitTest.php +++ /dev/null @@ -1,385 +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\Tests; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\VarExporter\ProxyHelper; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildMagicClass; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildStdClass; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildTestClass; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ClassWithUninitializedObjectProperty; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\LazyClass; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\MagicClass; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ReadOnlyClass; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\TestClass; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\AsymmetricVisibility; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\Hooked; -use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\HookedWithDefaultValue; -use Symfony\Component\VarExporter\Tests\Fixtures\SimpleObject; - -/** - * @group legacy - */ -class LegacyLazyGhostTraitTest extends TestCase -{ - public function testGetPublic() - { - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) { - $ghost->__construct(); - }); - - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); - $this->assertSame(-4, $instance->public); - $this->assertSame(4, $instance->publicReadonly); - } - - public function testGetPrivate() - { - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) { - $ghost->__construct(); - }); - - $r = new \ReflectionProperty(TestClass::class, 'private'); - - $this->assertSame(-3, $r->getValue($instance)); - } - - public function testIssetPublic() - { - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) { - $ghost->__construct(); - }); - - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); - $this->assertTrue(isset($instance->public)); - $this->assertSame(4, $instance->publicReadonly); - } - - public function testUnsetPublic() - { - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) { - $ghost->__construct(); - }); - - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); - unset($instance->public); - $this->assertSame(4, $instance->publicReadonly); - $this->expectException(\BadMethodCallException::class); - $this->expectExceptionMessage('__isset(public)'); - isset($instance->public); - } - - public function testSetPublic() - { - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) { - $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); - } - - public function testSetPrivate() - { - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) { - $ghost->__construct(); - }); - - $r = new \ReflectionProperty(TestClass::class, 'private'); - $r->setValue($instance, 3); - - $this->assertSame(3, $r->getValue($instance)); - } - - public function testClone() - { - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) { - $ghost->__construct(); - }); - - $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)); - - $clone = clone $clone; - $this->assertTrue($clone->resetLazyObject()); - } - - public function testSerialize() - { - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) { - $ghost->__construct(); - }); - - $serialized = serialize($instance); - $this->assertStringNotContainsString('lazyObjectState', $serialized); - - $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()); - $this->assertTrue($clone->isLazyObjectInitialized()); - } - - /** - * @dataProvider provideMagicClass - */ - public function testMagicClass(MagicClass $instance) - { - $this->assertSame('bar', $instance->foo); - $instance->foo = 123; - $this->assertSame(123, $instance->foo); - $this->assertTrue(isset($instance->foo)); - unset($instance->foo); - $this->assertFalse(isset($instance->foo)); - - $clone = clone $instance; - $this->assertSame(0, $instance->cloneCounter); - $this->assertSame(1, $clone->cloneCounter); - - $instance->bar = 123; - $serialized = serialize($instance); - $clone = unserialize($serialized); - - if ($instance instanceof ChildMagicClass) { - // ChildMagicClass redefines the $data property but not the __sleep() method - $this->assertFalse(isset($clone->bar)); - } else { - $this->assertSame(123, $clone->bar); - } - } - - public static function provideMagicClass() - { - yield [new MagicClass()]; - - yield [ChildMagicClass::createLazyGhost(function (ChildMagicClass $instance) { - $instance->__construct(); - })]; - } - - public function testResetLazyGhost() - { - $instance = ChildMagicClass::createLazyGhost(function (ChildMagicClass $instance) { - $instance->__construct(); - }); - - $instance->foo = 234; - $this->assertTrue($instance->resetLazyObject()); - $this->assertFalse($instance->isLazyObjectInitialized()); - $this->assertSame('bar', $instance->foo); - } - - public function testFullInitialization() - { - $counter = 0; - $instance = ChildTestClass::createLazyGhost(function (ChildTestClass $ghost) use (&$counter) { - ++$counter; - $ghost->__construct(); - }); - - $this->assertFalse($instance->isLazyObjectInitialized()); - $this->assertTrue(isset($instance->public)); - $this->assertTrue($instance->isLazyObjectInitialized()); - $this->assertSame(-4, $instance->public); - $this->assertSame(4, $instance->publicReadonly); - $this->assertSame(1, $counter); - } - - public function testSetStdClassProperty() - { - $instance = ChildStdClass::createLazyGhost(function (ChildStdClass $ghost) { - }); - - $instance->public = 12; - $this->assertSame(12, $instance->public); - } - - public function testLazyClass() - { - $obj = new LazyClass(fn ($proxy) => $proxy->public = 123); - - $this->assertSame(123, $obj->public); - } - - public function testReflectionPropertyGetValue() - { - $obj = TestClass::createLazyGhost(fn ($proxy) => $proxy->__construct()); - - $r = new \ReflectionProperty($obj, 'private'); - - $this->assertSame(-3, $r->getValue($obj)); - } - - public function testIndirectModification() - { - $obj = new class { - public array $foo; - }; - $proxy = $this->createLazyGhost($obj::class, fn () => null); - - $proxy->foo[] = 123; - - $this->assertSame([123], $proxy->foo); - } - - public function testReadOnlyClass() - { - $proxy = $this->createLazyGhost(ReadOnlyClass::class, fn ($proxy) => $proxy->__construct(123)); - - $this->assertSame(123, $proxy->foo); - } - - public function testAccessingUninializedPropertyWithoutLazyGhost() - { - $object = new ClassWithUninitializedObjectProperty(); - - $this->expectException(\Error::class); - $this->expectExceptionCode(0); - $this->expectExceptionMessage('Typed property Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ClassWithUninitializedObjectProperty::$property must not be accessed before initialization'); - - $object->property; - } - - public function testAccessingUninializedPropertyWithLazyGhost() - { - $object = $this->createLazyGhost(ClassWithUninitializedObjectProperty::class, function ($instance) {}); - - $this->expectException(\Error::class); - $this->expectExceptionCode(0); - $this->expectExceptionMessage('Typed property Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ClassWithUninitializedObjectProperty::$property must not be accessed before initialization'); - - $object->property; - } - - public function testNormalization() - { - $object = $this->createLazyGhost(SimpleObject::class, function ($instance) {}); - - $loader = new AttributeLoader(); - $metadataFactory = new ClassMetadataFactory($loader); - $serializer = new ObjectNormalizer($metadataFactory); - - $output = $serializer->normalize($object); - - $this->assertSame(['property' => 'property', 'method' => 'method'], $output); - } - - public function testReinitLazyGhost() - { - $object = TestClass::createLazyGhost(function ($p) { $p->public = 2; }); - - $this->assertSame(2, $object->public); - - TestClass::createLazyGhost(function ($p) { $p->public = 3; }, null, $object); - - $this->assertSame(3, $object->public); - } - - public function testPropertyHooks() - { - $initialized = false; - $object = $this->createLazyGhost(Hooked::class, function ($instance) use (&$initialized) { - $initialized = true; - }); - - $this->assertSame(123, $object->notBacked); - $this->assertFalse($initialized); - $this->assertSame(234, $object->backed); - $this->assertTrue($initialized); - - $initialized = false; - $object = $this->createLazyGhost(Hooked::class, function ($instance) use (&$initialized) { - $initialized = true; - }); - - $object->backed = 345; - $this->assertTrue($initialized); - $this->assertSame(345, $object->backed); - } - - public function testPropertyHooksWithDefaultValue() - { - $initialized = false; - $object = $this->createLazyGhost(HookedWithDefaultValue::class, function ($instance) use (&$initialized) { - $initialized = true; - }); - - $this->assertSame(321, $object->backedIntWithDefault); - $this->assertSame('321', $object->backedStringWithDefault); - $this->assertSame(false, $object->backedBoolWithDefault); - $this->assertTrue($initialized); - - $initialized = false; - $object = $this->createLazyGhost(HookedWithDefaultValue::class, function ($instance) use (&$initialized) { - $initialized = true; - }); - $object->backedIntWithDefault = 654; - $object->backedStringWithDefault = '654'; - $object->backedBoolWithDefault = true; - $this->assertTrue($initialized); - $this->assertSame(654, $object->backedIntWithDefault); - $this->assertSame('654', $object->backedStringWithDefault); - $this->assertSame(true, $object->backedBoolWithDefault); - } - - public function testAsymmetricVisibility() - { - $object = $this->createLazyGhost(AsymmetricVisibility::class, function ($instance) { - $instance->__construct(123, 234); - }); - - $this->assertSame(123, $object->foo); - $this->assertSame(234, $object->getBar()); - - $object = $this->createLazyGhost(AsymmetricVisibility::class, function ($instance) { - $instance->__construct(123, 234); - }); - - $this->assertSame(234, $object->getBar()); - $this->assertSame(123, $object->foo); - } - - /** - * @template T - * - * @param class-string $class - * - * @return T - */ - private function createLazyGhost(string $class, \Closure $initializer, ?array $skippedProperties = null): object - { - $r = new \ReflectionClass($class); - - if (str_contains($class, "\0")) { - $class = __CLASS__.'\\'.debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'].'_L'.$r->getStartLine(); - class_alias($r->name, $class); - } - $proxy = str_replace($r->name, $class, ProxyHelper::generateLazyGhost($r)); - $class = str_replace('\\', '_', $class).'_'.md5($proxy); - - if (!class_exists($class, false)) { - eval(($r->isReadOnly() ? 'readonly ' : '').'class '.$class.' '.$proxy); - } - - return $class::createLazyGhost($initializer, $skippedProperties); - } -} diff --git a/src/Symfony/Component/VarExporter/composer.json b/src/Symfony/Component/VarExporter/composer.json index 16981825f54c3..a3ced35d5ace7 100644 --- a/src/Symfony/Component/VarExporter/composer.json +++ b/src/Symfony/Component/VarExporter/composer.json @@ -16,8 +16,7 @@ } ], "require": { - "php": ">=8.4", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.4" }, "require-dev": { "symfony/property-access": "^7.4|^8.0",