diff --git a/Internal/LazyObjectRegistry.php b/Internal/LazyObjectRegistry.php index 2ce7df1..c78bdcf 100644 --- a/Internal/LazyObjectRegistry.php +++ b/Internal/LazyObjectRegistry.php @@ -45,10 +45,7 @@ class LazyObjectRegistry */ public static $parentMethods = []; - /** - * @var LazyObjectState - */ - public static $noInitializerState; + public static ?\Closure $noInitializerState = null; public static function getClassResetters($class) { diff --git a/Internal/LazyObjectState.php b/Internal/LazyObjectState.php index 99f721d..2f649dd 100644 --- a/Internal/LazyObjectState.php +++ b/Internal/LazyObjectState.php @@ -37,6 +37,8 @@ class LazyObjectState */ public int $status = 0; + public object $realInstance; + public function __construct(public readonly \Closure|array $initializer, $skippedProperties = []) { $this->skippedProperties = $skippedProperties; diff --git a/Internal/LazyObjectTrait.php b/Internal/LazyObjectTrait.php new file mode 100644 index 0000000..cccdf6c --- /dev/null +++ b/Internal/LazyObjectTrait.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +if (\PHP_VERSION_ID >= 80300) { + /** + * @internal + */ + trait LazyObjectTrait + { + private readonly LazyObjectState $lazyObjectState; + } +} else { + /** + * @internal + */ + trait LazyObjectTrait + { + private LazyObjectState $lazyObjectState; + } +} diff --git a/LICENSE b/LICENSE index 99757d5..7536cae 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2023 Fabien Potencier +Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LazyGhostTrait.php b/LazyGhostTrait.php index 92c240b..467e4d9 100644 --- a/LazyGhostTrait.php +++ b/LazyGhostTrait.php @@ -14,10 +14,11 @@ 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; trait LazyGhostTrait { - private LazyObjectState $lazyObjectState; + use LazyObjectTrait; /** * Creates a lazy-loading ghost instance. diff --git a/LazyProxyTrait.php b/LazyProxyTrait.php index 71ecb5b..153c382 100644 --- a/LazyProxyTrait.php +++ b/LazyProxyTrait.php @@ -15,17 +15,17 @@ 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; trait LazyProxyTrait { - private LazyObjectState $lazyObjectState; - private object $lazyObjectReal; + use LazyObjectTrait; /** * Creates a lazy-loading virtual proxy. * * @param \Closure():object $initializer Returns the proxied object - * @param static|null $instance + * @param static|null $instance */ public static function createLazyProxy(\Closure $initializer, object $instance = null): static { @@ -52,11 +52,7 @@ public static function createLazyProxy(\Closure $initializer, object $instance = */ public function isLazyObjectInitialized(bool $partial = false): bool { - if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState) { - return true; - } - - return \array_key_exists("\0".self::class."\0lazyObjectReal", (array) $this); + return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer; } /** @@ -64,8 +60,8 @@ public function isLazyObjectInitialized(bool $partial = false): bool */ public function initializeLazyObject(): parent { - if (isset($this->lazyObjectReal)) { - return $this->lazyObjectReal; + if ($state = $this->lazyObjectState ?? null) { + return $state->realInstance ??= ($state->initializer)(); } return $this; @@ -76,13 +72,11 @@ public function initializeLazyObject(): parent */ public function resetLazyObject(): bool { - if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState) { + if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) { return false; } - if (\array_key_exists("\0".self::class."\0lazyObjectReal", (array) $this)) { - unset($this->lazyObjectReal); - } + unset($this->lazyObjectState->realInstance); return true; } @@ -98,14 +92,7 @@ public function &__get($name): mixed if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { if ($state = $this->lazyObjectState ?? null) { - if ('lazyObjectReal' === $name && self::class === $scope) { - $this->lazyObjectReal = ($state->initializer)(); - - return $this->lazyObjectReal; - } - if (isset($this->lazyObjectReal)) { - $instance = $this->lazyObjectReal; - } + $instance = $state->realInstance ??= ($state->initializer)(); } $parent = 2; goto get_in_scope; @@ -113,8 +100,8 @@ public function &__get($name): mixed } $parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']; - if (isset($this->lazyObjectReal)) { - $instance = $this->lazyObjectReal; + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); } else { if (2 === $parent) { return parent::__get($name); @@ -174,22 +161,15 @@ public function __set($name, $value): void $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if (isset($this->lazyObjectState)) { - if ('lazyObjectReal' === $name && self::class === $scope) { - $this->lazyObjectReal = $value; - - return; - } - if (isset($this->lazyObjectReal)) { - $instance = $this->lazyObjectReal; - } + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); } goto set_in_scope; } } - if (isset($this->lazyObjectReal)) { - $instance = $this->lazyObjectReal; + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { parent::__set($name, $value); @@ -216,22 +196,15 @@ public function __isset($name): bool $scope = Registry::getScope($propertyScopes, $class, $name); if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if (isset($this->lazyObjectState)) { - if ('lazyObjectReal' === $name && self::class === $scope) { - $state = $this->lazyObjectState ?? null; - - return null !== $this->lazyObjectReal = $state ? ($state->initializer)() : null; - } - if (isset($this->lazyObjectReal)) { - $instance = $this->lazyObjectReal; - } + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); } goto isset_in_scope; } } - if (isset($this->lazyObjectReal)) { - $instance = $this->lazyObjectReal; + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { return parent::__isset($name); } @@ -256,22 +229,15 @@ public function __unset($name): void $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if (isset($this->lazyObjectState)) { - if ('lazyObjectReal' === $name && self::class === $scope) { - unset($this->lazyObjectReal); - - return; - } - if (isset($this->lazyObjectReal)) { - $instance = $this->lazyObjectReal; - } + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); } goto unset_in_scope; } } - if (isset($this->lazyObjectReal)) { - $instance = $this->lazyObjectReal; + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { parent::__unset($name); @@ -298,26 +264,30 @@ public function __clone(): void return; } - if (\array_key_exists("\0".self::class."\0lazyObjectReal", (array) $this)) { - $this->lazyObjectReal = clone $this->lazyObjectReal; - } - if ($state = $this->lazyObjectState ?? null) { - $this->lazyObjectState = clone $state; + $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 (!isset($this->lazyObjectReal) && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { + 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)(); + } } - unset($properties["\0$class\0lazyObjectState"]); - if (isset($this->lazyObjectReal) || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { + if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { return $properties; } @@ -341,17 +311,18 @@ public function __unserialize(array $data): void { $class = self::class; - if (isset($data["\0$class\0lazyObjectReal"])) { + 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 (1 < \count($data)) { + if ($data) { PublicHydrator::hydrate($this, $data); - } else { - $this->lazyObjectReal = $data["\0$class\0lazyObjectReal"]; } - $this->lazyObjectState = Registry::$noInitializerState ??= new LazyObjectState(static fn () => throw new \LogicException('Lazy proxy has no initializer.')); + $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 { diff --git a/ProxyHelper.php b/ProxyHelper.php index c323f0b..2e150cb 100644 --- a/ProxyHelper.php +++ b/ProxyHelper.php @@ -27,8 +27,8 @@ final class ProxyHelper */ public static function generateLazyGhost(\ReflectionClass $class): string { - if (\PHP_VERSION_ID >= 80200 && $class->isReadOnly()) { - throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is read-only.', $class->name)); + if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class->isReadOnly()) { + 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)); @@ -91,8 +91,8 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf if ($class?->isFinal()) { throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name)); } - if (\PHP_VERSION_ID >= 80200 && $class?->isReadOnly()) { - throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is read-only.', $class->name)); + if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class?->isReadOnly()) { + throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is readonly.', $class->name)); } $methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []]; @@ -142,15 +142,15 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf continue; } - $signature = self::exportSignature($method); - $parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}(...\\func_get_args())"; + $signature = self::exportSignature($method, true, $args); + $parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})"; if ($method->isStatic()) { $body = " $parentCall;"; } elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) { $body = <<lazyObjectReal)) { - \$this->lazyObjectReal->{$method->name}(...\\func_get_args()); + if (isset(\$this->lazyObjectState)) { + (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); } else { {$parentCall}; } @@ -171,8 +171,8 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf } $body = <<lazyObjectReal)) { - return \$this->lazyObjectReal->{$method->name}(...\\func_get_args()); + if (isset(\$this->lazyObjectState)) { + return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); } return {$parentCall}; @@ -195,17 +195,14 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf $methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods; } $body = $methods ? "\n".implode("\n\n", $methods)."\n" : ''; - $propertyScopes = $class ? substr(self::exportPropertyScopes($class->name), 1, -6) : ''; + $propertyScopes = $class ? self::exportPropertyScopes($class->name) : '[]'; return << [self::class, 'lazyObjectReal', null], - "\\0".self::class."\\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],{$propertyScopes} - ]; + private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes}; {$body}} // Help opcache.preload discover always-needed symbols @@ -216,8 +213,11 @@ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); EOPHP; } - public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true): string + public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, string &$args = null): string { + $hasByRef = false; + $args = ''; + $param = null; $parameters = []; foreach ($function->getParameters() as $param) { $parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '') @@ -225,6 +225,16 @@ public static function exportSignature(\ReflectionFunctionAbstract $function, bo .($param->isPassedByReference() ? '&' : '') .($param->isVariadic() ? '...' : '').'$'.$param->name .($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param) : ''); + $hasByRef = $hasByRef || $param->isPassedByReference(); + $args .= ($param->isVariadic() ? '...$' : '$').$param->name.', '; + } + + if (!$param || !$hasByRef) { + $args = '...\func_get_args()'; + } elseif ($param->isVariadic()) { + $args = substr($args, 0, -2); + } else { + $args .= sprintf('...\array_slice(\func_get_args(), %d)', \count($parameters)); } $signature = 'function '.($function->returnsReference() ? '&' : '') diff --git a/Tests/Fixtures/LazyGhost/ReadOnlyClass.php b/Tests/Fixtures/LazyGhost/ReadOnlyClass.php new file mode 100644 index 0000000..52f49e2 --- /dev/null +++ b/Tests/Fixtures/LazyGhost/ReadOnlyClass.php @@ -0,0 +1,20 @@ + + * + * 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/Tests/InstantiatorTest.php b/Tests/InstantiatorTest.php index becf420..062d4ea 100644 --- a/Tests/InstantiatorTest.php +++ b/Tests/InstantiatorTest.php @@ -35,7 +35,7 @@ public function testFailingInstantiation(string $class) Instantiator::instantiate($class); } - public function provideFailingInstantiation() + public static function provideFailingInstantiation() { yield ['ReflectionClass']; yield ['SplHeap']; diff --git a/Tests/LazyGhostTraitTest.php b/Tests/LazyGhostTraitTest.php index 3ef2a8c..ae989ad 100644 --- a/Tests/LazyGhostTraitTest.php +++ b/Tests/LazyGhostTraitTest.php @@ -19,6 +19,7 @@ use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildTestClass; 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; class LazyGhostTraitTest extends TestCase @@ -148,7 +149,7 @@ public function testMagicClass(MagicClass $instance) $this->assertSame(123, $clone->bar); } - public function provideMagicClass() + public static function provideMagicClass() { yield [new MagicClass()]; @@ -407,6 +408,16 @@ public function testIndirectModification() $this->assertSame([123], $proxy->foo); } + /** + * @requires PHP 8.3 + */ + public function testReadOnlyClass() + { + $proxy = $this->createLazyGhost(ReadOnlyClass::class, fn ($proxy) => $proxy->__construct(123)); + + $this->assertSame(123, $proxy->foo); + } + /** * @template T * diff --git a/Tests/LazyProxyTraitTest.php b/Tests/LazyProxyTraitTest.php index 074934a..58d49e5 100644 --- a/Tests/LazyProxyTraitTest.php +++ b/Tests/LazyProxyTraitTest.php @@ -168,10 +168,10 @@ public function testDynamicProperty() $this->assertSame(1, $initCounter); $this->assertSame(123, $proxy->dynProp); $this->assertTrue(isset($proxy->dynProp)); - $this->assertCount(2, (array) $proxy); + $this->assertCount(1, (array) $proxy); unset($proxy->dynProp); $this->assertFalse(isset($proxy->dynProp)); - $this->assertCount(2, (array) $proxy); + $this->assertCount(1, (array) $proxy); } public function testStringMagicGet() @@ -250,9 +250,14 @@ public function testIndirectModification() */ public function testReadOnlyClass() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Cannot generate lazy proxy: class "Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\ReadOnlyClass" is read-only.'); - $this->createLazyProxy(ReadOnlyClass::class, fn () => new ReadOnlyClass(123)); + if (\PHP_VERSION_ID < 80300) { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot generate lazy proxy: class "Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\ReadOnlyClass" is readonly.'); + } + + $proxy = $this->createLazyProxy(ReadOnlyClass::class, fn () => new ReadOnlyClass(123)); + + $this->assertSame(123, $proxy->foo); } public function testLazyDecoratorClass() diff --git a/Tests/ProxyHelperTest.php b/Tests/ProxyHelperTest.php index 7745a77..806c5c9 100644 --- a/Tests/ProxyHelperTest.php +++ b/Tests/ProxyHelperTest.php @@ -26,7 +26,7 @@ public function testExportSignature(string $expected, \ReflectionMethod $method) $this->assertSame($expected, ProxyHelper::exportSignature($method)); } - public function provideExportSignature() + public static function provideExportSignature() { $methods = (new \ReflectionClass(TestForProxyHelper::class))->getMethods(); $source = file(__FILE__); @@ -66,33 +66,30 @@ public function testGenerateLazyProxy() { use \Symfony\Component\VarExporter\LazyProxyTrait; - private const LAZY_OBJECT_PROPERTY_SCOPES = [ - 'lazyObjectReal' => [self::class, 'lazyObjectReal', null], - "\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null], - ]; + private const LAZY_OBJECT_PROPERTY_SCOPES = []; public function foo1(): ?\Symfony\Component\VarExporter\Tests\Bar { - if (isset($this->lazyObjectReal)) { - return $this->lazyObjectReal->foo1(...\func_get_args()); + if (isset($this->lazyObjectState)) { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo1(...\func_get_args()); } return parent::foo1(...\func_get_args()); } - public function foo4(\Symfony\Component\VarExporter\Tests\Bar|string $b): void + public function foo4(\Symfony\Component\VarExporter\Tests\Bar|string $b, &$d): void { - if (isset($this->lazyObjectReal)) { - $this->lazyObjectReal->foo4(...\func_get_args()); + if (isset($this->lazyObjectState)) { + ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo4($b, $d, ...\array_slice(\func_get_args(), 2)); } else { - parent::foo4(...\func_get_args()); + parent::foo4($b, $d, ...\array_slice(\func_get_args(), 2)); } } protected function foo7() { - if (isset($this->lazyObjectReal)) { - return $this->lazyObjectReal->foo7(...\func_get_args()); + if (isset($this->lazyObjectState)) { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo7(...\func_get_args()); } return throw new \BadMethodCallException('Cannot forward abstract method "Symfony\Component\VarExporter\Tests\TestForProxyHelper::foo7()".'); @@ -116,15 +113,12 @@ public function testGenerateLazyProxyForInterfaces() { use \Symfony\Component\VarExporter\LazyProxyTrait; - private const LAZY_OBJECT_PROPERTY_SCOPES = [ - 'lazyObjectReal' => [self::class, 'lazyObjectReal', null], - "\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null], - ]; + private const LAZY_OBJECT_PROPERTY_SCOPES = []; public function initializeLazyObject(): \Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface1&\Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface2 { - if (isset($this->lazyObjectReal)) { - return $this->lazyObjectReal; + if ($state = $this->lazyObjectState ?? null) { + return $state->realInstance ??= ($state->initializer)(); } return $this; @@ -132,17 +126,17 @@ public function initializeLazyObject(): \Symfony\Component\VarExporter\Tests\Tes public function foo1(): ?\Symfony\Component\VarExporter\Tests\Bar { - if (isset($this->lazyObjectReal)) { - return $this->lazyObjectReal->foo1(...\func_get_args()); + if (isset($this->lazyObjectState)) { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo1(...\func_get_args()); } return throw new \BadMethodCallException('Cannot forward abstract method "Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface1::foo1()".'); } - public function foo2(?\Symfony\Component\VarExporter\Tests\Bar $b): \Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface2 + public function foo2(?\Symfony\Component\VarExporter\Tests\Bar $b, ...$d): \Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface2 { - if (isset($this->lazyObjectReal)) { - return $this->lazyObjectReal->foo2(...\func_get_args()); + if (isset($this->lazyObjectState)) { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo2(...\func_get_args()); } return throw new \BadMethodCallException('Cannot forward abstract method "Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface2::foo2()".'); @@ -170,8 +164,8 @@ public function testAttributes() public function foo(#[\SensitiveParameter] $a): int { - if (isset($this->lazyObjectReal)) { - return $this->lazyObjectReal->foo(...\func_get_args()); + if (isset($this->lazyObjectState)) { + return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->foo(...\func_get_args()); } return parent::foo(...\func_get_args()); @@ -202,7 +196,7 @@ public function foo1(): ?Bar { } - public function foo2(?Bar $b): ?self + public function foo2(?Bar $b, ...$d): ?self { } @@ -210,7 +204,7 @@ public function &foo3(Bar &$b, string &...$c) { } - public function foo4(Bar|string $b): void + public function foo4(Bar|string $b, &$d): void { } @@ -240,7 +234,7 @@ public function foo1(): ?Bar; interface TestForProxyHelperInterface2 { - public function foo2(?Bar $b): self; + public function foo2(?Bar $b, ...$d): self; public static function foo3(): string; } diff --git a/Tests/VarExporterTest.php b/Tests/VarExporterTest.php index 6dd133c..dfdd0b8 100644 --- a/Tests/VarExporterTest.php +++ b/Tests/VarExporterTest.php @@ -53,7 +53,7 @@ public function testFailingSerialization($value) } } - public function provideFailingSerialization() + public static function provideFailingSerialization() { yield [hash_init('md5')]; yield [new \ReflectionClass(\stdClass::class)]; @@ -115,7 +115,7 @@ public function testExport(string $testName, $value, bool $staticValueExpected = } } - public function provideExport() + public static function provideExport() { yield ['multiline-string', ["\0\0\r\nA" => "B\rC\n\n"], true]; yield ['lf-ending-string', "'BOOM'\n.var_dump(123)//'", true]; diff --git a/VarExporter.php b/VarExporter.php index c820fc9..c12eb4f 100644 --- a/VarExporter.php +++ b/VarExporter.php @@ -32,8 +32,8 @@ final class VarExporter /** * Exports a serializable PHP value to PHP code. * - * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise - * @param bool &$foundClasses Classes found in the value are added to this list as both keys and values + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * @param array &$foundClasses Classes found in the value are added to this list as both keys and values * * @throws ExceptionInterface When the provided value cannot be serialized */ diff --git a/composer.json b/composer.json index 67c4a27..83140ee 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "symfony/var-exporter", "type": "library", "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone", "lazy loading", "proxy"], + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone", "lazy-loading", "proxy"], "homepage": "https://symfony.com", "license": "MIT", "authors": [