From 27650efd912e33e6062650f678a5901fe198a3f5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 13 Nov 2023 16:08:57 +0100 Subject: [PATCH] [VarExporter] Drop support for partially initialized lazy object --- UPGRADE-7.0.md | 6 + .../Internal/LazyObjectRegistry.php | 8 +- .../VarExporter/Internal/LazyObjectState.php | 45 +--- .../Component/VarExporter/LazyGhostTrait.php | 84 +------- .../VarExporter/Tests/LazyGhostTraitTest.php | 200 +----------------- .../Component/VarExporter/composer.json | 3 +- 6 files changed, 27 insertions(+), 319 deletions(-) diff --git a/UPGRADE-7.0.md b/UPGRADE-7.0.md index 91282771faff7..481ffdad795ba 100644 --- a/UPGRADE-7.0.md +++ b/UPGRADE-7.0.md @@ -46,6 +46,7 @@ Components * [Translation](#Translation) * [Validator](#Validator) * [VarDumper](#VarDumper) + * [VarExporter](#VarExporter) * [Workflow](#Workflow) * [Yaml](#Yaml) @@ -608,6 +609,11 @@ VarDumper * Add parameter `string $label = null` to `VarDumper::dump()` * Require explicit argument when calling `VarDumper::setHandler()` +VarExporter +----------- + + * Remove support for per-property lazy-initializers + Workflow -------- diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php index fddc6fb3b9664..323e8cca27fb0 100644 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php +++ b/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php @@ -67,18 +67,18 @@ public static function getClassResetters($class) $resetters = []; foreach ($classProperties as $scope => $properties) { - $resetters[] = \Closure::bind(static function ($instance, $skippedProperties, $onlyProperties = null) use ($properties) { + $resetters[] = \Closure::bind(static function ($instance, $skippedProperties) use ($properties) { foreach ($properties as $name => $key) { - if (!\array_key_exists($key, $skippedProperties) && (null === $onlyProperties || \array_key_exists($key, $onlyProperties))) { + if (!\array_key_exists($key, $skippedProperties)) { unset($instance->$name); } } }, null, $scope); } - $resetters[] = static function ($instance, $skippedProperties, $onlyProperties = null) { + $resetters[] = static function ($instance, $skippedProperties) { foreach ((array) $instance as $name => $value) { - if ("\0" !== ($name[0] ?? '') && !\array_key_exists($name, $skippedProperties) && (null === $onlyProperties || \array_key_exists($name, $onlyProperties))) { + if ("\0" !== ($name[0] ?? '') && !\array_key_exists($name, $skippedProperties)) { unset($instance->$name); } } diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php index 2f649dd1ca481..2293031e4c107 100644 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php +++ b/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php @@ -39,10 +39,10 @@ class LazyObjectState public object $realInstance; - public function __construct(public readonly \Closure|array $initializer, $skippedProperties = []) + public function __construct(public readonly \Closure $initializer, $skippedProperties = []) { $this->skippedProperties = $skippedProperties; - $this->status = \is_array($initializer) ? self::STATUS_UNINITIALIZED_PARTIAL : self::STATUS_UNINITIALIZED_FULL; + $this->status = self::STATUS_UNINITIALIZED_FULL; } public function initialize($instance, $propertyName, $propertyScope) @@ -51,42 +51,6 @@ public function initialize($instance, $propertyName, $propertyScope) return self::STATUS_INITIALIZED_FULL; } - if (\is_array($this->initializer)) { - $class = $instance::class; - $propertyScope ??= $class; - $propertyScopes = Hydrator::$propertyScopes[$class]; - $propertyScopes[$k = "\0$propertyScope\0$propertyName"] ?? $propertyScopes[$k = "\0*\0$propertyName"] ?? $k = $propertyName; - - if ($initializer = $this->initializer[$k] ?? null) { - $value = $initializer(...[$instance, $propertyName, $propertyScope, LazyObjectRegistry::$defaultProperties[$class][$k] ?? null]); - $accessor = LazyObjectRegistry::$classAccessors[$propertyScope] ??= LazyObjectRegistry::getClassAccessors($propertyScope); - $accessor['set']($instance, $propertyName, $value); - - return $this->status = self::STATUS_INITIALIZED_PARTIAL; - } - - $status = self::STATUS_UNINITIALIZED_PARTIAL; - - if ($initializer = $this->initializer["\0"] ?? null) { - if (!\is_array($values = $initializer($instance, LazyObjectRegistry::$defaultProperties[$class]))) { - throw new \TypeError(sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values))); - } - $properties = (array) $instance; - foreach ($values as $key => $value) { - if ($k === $key) { - $status = self::STATUS_INITIALIZED_PARTIAL; - } - if (!\array_key_exists($key, $properties) && [$scope, $name, $readonlyScope] = $propertyScopes[$key] ?? null) { - $scope = $readonlyScope ?? ('*' !== $scope ? $scope : $class); - $accessor = LazyObjectRegistry::$classAccessors[$scope] ??= LazyObjectRegistry::getClassAccessors($scope); - $accessor['set']($instance, $name, $value); - } - } - } - - return $status; - } - $this->status = self::STATUS_INITIALIZED_FULL; try { @@ -111,7 +75,6 @@ public function reset($instance): void $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); $skippedProperties = $this->skippedProperties; $properties = (array) $instance; - $onlyProperties = \is_array($this->initializer) ? $this->initializer : null; foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) { $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; @@ -122,9 +85,9 @@ public function reset($instance): void } foreach (LazyObjectRegistry::$classResetters[$class] as $reset) { - $reset($instance, $skippedProperties, $onlyProperties); + $reset($instance, $skippedProperties); } - $this->status = self::STATUS_INITIALIZED_FULL === $this->status ? self::STATUS_UNINITIALIZED_FULL : self::STATUS_UNINITIALIZED_PARTIAL; + $this->status = self::STATUS_UNINITIALIZED_FULL; } } diff --git a/src/Symfony/Component/VarExporter/LazyGhostTrait.php b/src/Symfony/Component/VarExporter/LazyGhostTrait.php index 66345cdc4ad5c..b416d171b9e39 100644 --- a/src/Symfony/Component/VarExporter/LazyGhostTrait.php +++ b/src/Symfony/Component/VarExporter/LazyGhostTrait.php @@ -31,14 +31,8 @@ trait LazyGhostTrait * that the initializer doesn't initialize, if any * @param static|null $instance */ - public static function createLazyGhost(\Closure|array $initializer, array $skippedProperties = null, object $instance = null): static + public static function createLazyGhost(\Closure $initializer, array $skippedProperties = null, object $instance = null): static { - if (\is_array($initializer)) { - trigger_deprecation('symfony/var-exporter', '6.4', 'Per-property lazy-initializers are deprecated and won\'t be supported anymore in 7.0, use an object initializer instead.'); - } - - $onlyProperties = null === $skippedProperties && \is_array($initializer) ? $initializer : null; - if (self::class !== $class = $instance ? $instance::class : static::class) { $skippedProperties["\0".self::class."\0lazyObjectState"] = true; } elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { @@ -50,7 +44,7 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp $instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []); foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { - $reset($instance, $skippedProperties, $onlyProperties); + $reset($instance, $skippedProperties); } return $instance; @@ -67,25 +61,7 @@ public function isLazyObjectInitialized(bool $partial = false): bool return true; } - if (!\is_array($state->initializer)) { - return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status; - } - - $class = $this::class; - $properties = (array) $this; - - if ($partial) { - return (bool) array_intersect_key($state->initializer, $properties); - } - - $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); - foreach ($state->initializer as $key => $initializer) { - if (!\array_key_exists($key, $properties) && isset($propertyScopes[$key])) { - return false; - } - } - - return true; + return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status; } /** @@ -97,42 +73,8 @@ public function initializeLazyObject(): static return $this; } - if (!\is_array($state->initializer)) { - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, '', null); - } - - return $this; - } - - $values = isset($state->initializer["\0"]) ? null : []; - - $class = $this::class; - $properties = (array) $this; - $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); - foreach ($state->initializer as $key => $initializer) { - if (\array_key_exists($key, $properties) || ![$scope, $name, $readonlyScope] = $propertyScopes[$key] ?? null) { - continue; - } - $scope = $readonlyScope ?? ('*' !== $scope ? $scope : $class); - - if (null === $values) { - if (!\is_array($values = ($state->initializer["\0"])($this, Registry::$defaultProperties[$class]))) { - throw new \TypeError(sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values))); - } - - if (\array_key_exists($key, $properties = (array) $this)) { - continue; - } - } - - if (\array_key_exists($key, $values)) { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['set']($this, $name, $properties[$key] = $values[$key]); - } else { - $state->initialize($this, $name, $scope); - $properties = (array) $this; - } + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, '', null); } return $this; @@ -163,9 +105,8 @@ public function &__get($name): mixed $scope = Registry::getScope($propertyScopes, $class, $name); $state = $this->lazyObjectState ?? null; - if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope) - ) { + if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) { + $state->initialize($this, $name, $readonlyScope ?? $scope); goto get_in_scope; } } @@ -261,9 +202,8 @@ public function __isset($name): bool $scope = Registry::getScope($propertyScopes, $class, $name); $state = $this->lazyObjectState ?? null; - if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope) - ) { + if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) { + $state->initialize($this, $name, $readonlyScope ?? $scope); goto isset_in_scope; } } @@ -362,7 +302,7 @@ public function __destruct() { $state = $this->lazyObjectState ?? null; - if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) { + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state?->status) { return; } @@ -373,9 +313,7 @@ public function __destruct() private function setLazyObjectAsInitialized(bool $initialized): void { - $state = $this->lazyObjectState ?? null; - - if ($state && !\is_array($state->initializer)) { + if ($state = $this->lazyObjectState ?? null) { $state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL; } } diff --git a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php index ee8596593e9a8..c87adb008c8c6 100644 --- a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php +++ b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\VarExporter\Tests; use PHPUnit\Framework\TestCase; -use Symfony\Component\VarExporter\Internal\LazyObjectState; use Symfony\Component\VarExporter\ProxyHelper; use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildMagicClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildStdClass; @@ -186,111 +185,6 @@ public function testFullInitialization() $this->assertSame(1, $counter); } - /** - * @group legacy - */ - public function testPartialInitialization() - { - $counter = 0; - $instance = ChildTestClass::createLazyGhost([ - 'public' => static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) { - ++$counter; - - return 4 === $default ? 123 : -1; - }, - 'publicReadonly' => static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) { - ++$counter; - - return 234; - }, - "\0*\0protected" => static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) { - ++$counter; - - return 5 === $default ? 345 : -1; - }, - "\0*\0protectedReadonly" => static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) { - ++$counter; - - return 456; - }, - "\0".TestClass::class."\0private" => static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) { - ++$counter; - - return 3 === $default ? 567 : -1; - }, - "\0".ChildTestClass::class."\0private" => static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) { - ++$counter; - - return 6 === $default ? 678 : -1; - }, - 'dummyProperty' => fn () => 123, - ]); - - $this->assertSame(["\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); - $this->assertFalse($instance->isLazyObjectInitialized()); - $this->assertSame(123, $instance->public); - $this->assertFalse($instance->isLazyObjectInitialized()); - $this->assertTrue($instance->isLazyObjectInitialized(true)); - $this->assertSame(['public', "\0".TestClass::class."\0lazyObjectState"], array_keys((array) $instance)); - $this->assertSame(1, $counter); - - $instance->initializeLazyObject(); - $this->assertTrue($instance->isLazyObjectInitialized()); - $this->assertSame(123, $instance->public); - $this->assertSame(6, $counter); - - $properties = (array) $instance; - $this->assertInstanceOf(LazyObjectState::class, $properties["\0".TestClass::class."\0lazyObjectState"]); - unset($properties["\0".TestClass::class."\0lazyObjectState"]); - $this->assertSame(array_keys((array) new ChildTestClass()), array_keys($properties)); - $this->assertSame([123, 345, 456, 567, 234, 678], array_values($properties)); - } - - /** - * @group legacy - */ - public function testPartialInitializationWithReset() - { - $initializer = static fn (ChildTestClass $instance, string $property, ?string $scope, mixed $default) => 234; - $instance = ChildTestClass::createLazyGhost([ - 'public' => $initializer, - 'publicReadonly' => $initializer, - "\0*\0protected" => $initializer, - ]); - - $r = new \ReflectionProperty($instance, 'public'); - $r->setValue($instance, 123); - - $this->assertFalse($instance->isLazyObjectInitialized()); - $this->assertSame(234, $instance->publicReadonly); - $this->assertFalse($instance->isLazyObjectInitialized()); - $this->assertSame(123, $instance->public); - - $this->assertTrue($instance->resetLazyObject()); - $this->assertSame(234, $instance->publicReadonly); - $this->assertSame(234, $instance->public); - - $instance = ChildTestClass::createLazyGhost(['public' => $initializer]); - - $instance->resetLazyObject(); - - $instance->public = 123; - $this->assertSame(123, $instance->public); - - $this->assertTrue($instance->resetLazyObject()); - $this->assertSame(234, $instance->public); - } - - /** - * @group legacy - */ - public function testPartialInitializationWithNastyPassByRef() - { - $instance = ChildTestClass::createLazyGhost(['public' => fn (ChildTestClass $instance, string &$property, ?string &$scope, mixed $default) => $property = $scope = 123]); - - $this->assertSame(123, $instance->public); - } - public function testSetStdClassProperty() { $instance = ChildStdClass::createLazyGhost(function (ChildStdClass $ghost) { @@ -316,98 +210,6 @@ public function testReflectionPropertyGetValue() $this->assertSame(-3, $r->getValue($obj)); } - /** - * @group legacy - */ - public function testFullPartialInitialization() - { - $counter = 0; - $initializer = static fn (ChildTestClass $instance, string $property, ?string $scope, mixed $default) => 234; - $instance = ChildTestClass::createLazyGhost([ - 'public' => $initializer, - 'publicReadonly' => $initializer, - "\0*\0protected" => $initializer, - "\0" => function ($obj, $defaults) use (&$instance, &$counter) { - $counter += 1000; - $this->assertSame($instance, $obj); - - return [ - 'public' => 345, - 'publicReadonly' => 456, - "\0*\0protected" => 567, - ] + $defaults; - }, - ]); - - $this->assertSame($instance, $instance->initializeLazyObject()); - $this->assertSame(345, $instance->public); - $this->assertSame(456, $instance->publicReadonly); - $this->assertSame(6, ((array) $instance)["\0".ChildTestClass::class."\0private"]); - $this->assertSame(3, ((array) $instance)["\0".TestClass::class."\0private"]); - $this->assertSame(1000, $counter); - } - - /** - * @group legacy - */ - public function testPartialInitializationFallback() - { - $counter = 0; - $instance = ChildTestClass::createLazyGhost([ - "\0" => function ($obj) use (&$instance, &$counter) { - $counter += 1000; - $this->assertSame($instance, $obj); - - return [ - 'public' => 345, - 'publicReadonly' => 456, - "\0*\0protected" => 567, - ]; - }, - ], []); - - $this->assertSame(345, $instance->public); - $this->assertSame(456, $instance->publicReadonly); - $this->assertSame(567, ((array) $instance)["\0*\0protected"]); - $this->assertSame(1000, $counter); - } - - /** - * @group legacy - */ - public function testFullInitializationAfterPartialInitialization() - { - $counter = 0; - $initializer = static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) { - ++$counter; - - return 234; - }; - $instance = ChildTestClass::createLazyGhost([ - 'public' => $initializer, - 'publicReadonly' => $initializer, - "\0*\0protected" => $initializer, - "\0" => function ($obj, $defaults) use (&$instance, &$counter) { - $counter += 1000; - $this->assertSame($instance, $obj); - - return [ - 'public' => 345, - 'publicReadonly' => 456, - "\0*\0protected" => 567, - ] + $defaults; - }, - ]); - - $this->assertSame(234, $instance->public); - $this->assertSame($instance, $instance->initializeLazyObject()); - $this->assertSame(234, $instance->public); - $this->assertSame(456, $instance->publicReadonly); - $this->assertSame(6, ((array) $instance)["\0".ChildTestClass::class."\0private"]); - $this->assertSame(3, ((array) $instance)["\0".TestClass::class."\0private"]); - $this->assertSame(1001, $counter); - } - public function testIndirectModification() { $obj = new class() { @@ -437,7 +239,7 @@ public function testReadOnlyClass() * * @return T */ - private function createLazyGhost(string $class, \Closure|array $initializer, array $skippedProperties = null): object + private function createLazyGhost(string $class, \Closure $initializer, array $skippedProperties = null): object { $r = new \ReflectionClass($class); diff --git a/src/Symfony/Component/VarExporter/composer.json b/src/Symfony/Component/VarExporter/composer.json index 418268b2fc8c6..7a6f7b6071605 100644 --- a/src/Symfony/Component/VarExporter/composer.json +++ b/src/Symfony/Component/VarExporter/composer.json @@ -16,8 +16,7 @@ } ], "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.2" }, "require-dev": { "symfony/var-dumper": "^6.4|^7.0"