From 38fe500337ff27c83c56bb38ed126a6dc8ddaa7c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 27 Apr 2022 15:54:23 -0700 Subject: [PATCH] Fix enums and new in initializers as default values on PHP 8.1 --- .../Factory/AbstractBaseFactory.php | 2 +- src/ProxyManager/Generator/ClassGenerator.php | 20 ++++++- src/ProxyManager/Generator/ValueGenerator.php | 56 +++++++++++++++++++ .../MethodGenerator/CallInitializer.php | 16 +++++- .../MethodGenerator/RemoteObjectMethod.php | 27 +++++---- .../FatalPreventionFunctionalTest.php | 2 +- .../MultipleProxyGenerationTest.php | 5 ++ .../BaseGeneratorStrategyTest.php | 2 +- .../EvaluatingGeneratorStrategyTest.php | 2 +- .../FileWriterGeneratorStrategyTest.php | 2 +- .../AbstractProxyGeneratorTest.php | 7 ++- .../AccessInterceptorScopeLocalizerTest.php | 2 +- .../LazyLoadingGhostGeneratorTest.php | 2 +- .../NullObjectGeneratorTest.php | 7 ++- .../RemoteObjectMethodTest.php | 33 ++++++----- .../RemoteObjectGeneratorTest.php | 7 ++- .../ClassWithPhp81Defaults.php | 24 ++++++++ 17 files changed, 174 insertions(+), 42 deletions(-) create mode 100644 src/ProxyManager/Generator/ValueGenerator.php create mode 100644 tests/ProxyManagerTestAsset/ClassWithPhp81Defaults.php diff --git a/src/ProxyManager/Factory/AbstractBaseFactory.php b/src/ProxyManager/Factory/AbstractBaseFactory.php index 50d7fed8..e39b0399 100644 --- a/src/ProxyManager/Factory/AbstractBaseFactory.php +++ b/src/ProxyManager/Factory/AbstractBaseFactory.php @@ -4,9 +4,9 @@ namespace ProxyManager\Factory; -use Laminas\Code\Generator\ClassGenerator; use OutOfBoundsException; use ProxyManager\Configuration; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; use ProxyManager\Signature\Exception\InvalidSignatureException; use ProxyManager\Signature\Exception\MissingSignatureException; diff --git a/src/ProxyManager/Generator/ClassGenerator.php b/src/ProxyManager/Generator/ClassGenerator.php index fd014334..6d36d188 100644 --- a/src/ProxyManager/Generator/ClassGenerator.php +++ b/src/ProxyManager/Generator/ClassGenerator.php @@ -5,14 +5,30 @@ namespace ProxyManager\Generator; use Laminas\Code\Generator\ClassGenerator as LaminasClassGenerator; +use ProxyManager\Generator\ValueGenerator; +use ReflectionParameter; + +use function method_exists; /** * Class generator that ensures that interfaces/classes that are implemented/extended are FQCNs * * @internal do not use this in your code: it is only here for internal use - * @deprecated this class was in use due to parent implementation not receiving prompt bugfixes, but - * `laminas/laminas-code` is actively maintained and receives quick release iterations. */ class ClassGenerator extends LaminasClassGenerator { + public function generate(): string + { + $extendedClass = $this->getExtendedClass(); + + foreach ($this->getMethods() as $method) { + foreach ($method->getParameters() as $parameter) { + if ($extendedClass && method_exists($extendedClass, $method->getName()) && $default = $parameter->getDefaultValue()) { + $parameter->setDefaultValue(new ValueGenerator($default, new ReflectionParameter([$extendedClass, $method->getName()], $parameter->getName()))); + } + } + } + + return parent::generate(); + } } diff --git a/src/ProxyManager/Generator/ValueGenerator.php b/src/ProxyManager/Generator/ValueGenerator.php new file mode 100644 index 00000000..9517cc5b --- /dev/null +++ b/src/ProxyManager/Generator/ValueGenerator.php @@ -0,0 +1,56 @@ + $v) { + $this->$k = $v; + } + + $this->reflection = $reflection; + } + + public function generate(): string + { + if ($this->value instanceof UnitEnum) { + return self::export($this->value); + } + + try { + return parent::generate(); + } catch (RuntimeException $e) { + // FIXME: indentation and root namespace + return rtrim(substr(explode('$' . $this->reflection->name . ' = ', (string) $this->reflection, 2)[1], 0, -2)); + } + } + + public static function export($value): string + { + $value = var_export($value, true); + $parts = preg_split('{(\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')}', $value, -1, PREG_SPLIT_DELIM_CAPTURE); + + foreach ($parts as $i => $part) { + if ($part !== '' && $i % 2 === 0) { + $parts[$i] = preg_replace('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?&V)(?:\\\\(?&V))*+::/', '\\\\$0', $part); + } + } + + return implode('', $parts); + } +} diff --git a/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php b/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php index 493c212d..1ff555dc 100644 --- a/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php +++ b/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php @@ -8,15 +8,20 @@ use Laminas\Code\Generator\PropertyGenerator; use ProxyManager\Generator\MethodGenerator; use ProxyManager\Generator\Util\IdentifierSuffixer; +use ProxyManager\Generator\ValueGenerator; use ProxyManager\ProxyGenerator\Util\Properties; use ReflectionProperty; use function array_map; use function implode; +use function preg_replace; +use function preg_split; use function sprintf; use function str_replace; use function var_export; +use const PREG_SPLIT_DELIM_CAPTURE; + /** * Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::isProxyInitialized} * for lazy loading value holder objects @@ -170,6 +175,15 @@ private function getExportedPropertyDefaultValue(ReflectionProperty $property): $name = $property->getName(); $defaults = $property->getDeclaringClass()->getDefaultProperties(); - return var_export($defaults[$name] ?? null, true); + $value = var_export($defaults[$name] ?? null, true); + $parts = preg_split('{(\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')}', $value, -1, PREG_SPLIT_DELIM_CAPTURE); + + foreach ($parts as $i => $part) { + if ($part !== '' && $i % 2 === 0) { + $parts[$i] = preg_replace('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?&V)(?:\\\\(?&V))*+::/', '\\\\$0', $part); + } + } + + return implode('', $parts); } } diff --git a/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php b/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php index 799a3806..3e5e98f8 100644 --- a/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php +++ b/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php @@ -21,10 +21,12 @@ class RemoteObjectMethod extends MethodGenerator { private const TEMPLATE = <<<'PHP' -$defaultValues = #DEFAULT_VALUES#; -$declaredParameterCount = #PARAMETER_COUNT#; +static $reflection; -$args = \func_get_args() + $defaultValues; +$args = \func_get_args(); + +switch (\func_num_args()) {#DEFAULT_VALUES# +} #PROXIED_RETURN# PHP; @@ -41,16 +43,14 @@ public static function generateMethod( . ', ' . var_export($originalMethod->getName(), true) . ', $args);' . "\n\n" . ProxiedMethodReturnExpression::generate('$return', $originalMethod); - $defaultValues = self::getDefaultValuesForMethod($originalMethod); - $declaredParameterCount = count($originalMethod->getParameters()); + $defaultValues = self::getDefaultValuesForMethod($originalMethod); $method->setBody( strtr( self::TEMPLATE, [ '#PROXIED_RETURN#' => $proxiedReturn, - '#DEFAULT_VALUES#' => var_export($defaultValues, true), - '#PARAMETER_COUNT#' => var_export($declaredParameterCount, true), + '#DEFAULT_VALUES#' => $defaultValues, ] ) ); @@ -58,14 +58,13 @@ public static function generateMethod( return $method; } - /** @psalm-return list */ - private static function getDefaultValuesForMethod(MethodReflection $originalMethod): array + private static function getDefaultValuesForMethod(MethodReflection $originalMethod): string { - $defaultValues = []; - foreach ($originalMethod->getParameters() as $parameter) { + $parameters = sprintf('($reflection ?? $reflection = (new \ReflectionMethod(\'%s\', \'%s\'))->getParameters())', $originalMethod->class, $originalMethod->name); + $defaultValues = ''; + foreach ($originalMethod->getParameters() as $i => $parameter) { if ($parameter->isOptional() && $parameter->isDefaultValueAvailable()) { - /** @psalm-var int|float|bool|array|string|null */ - $defaultValues[] = $parameter->getDefaultValue(); + $defaultValues .= "\n case $i: \$args[] = {$parameters}[$i]->getDefaultValue();"; continue; } @@ -73,7 +72,7 @@ private static function getDefaultValuesForMethod(MethodReflection $originalMeth continue; } - $defaultValues[] = null; + $defaultValues .= "\n case $i: \$args[] = NULL;"; } return $defaultValues; diff --git a/tests/ProxyManagerTest/Functional/FatalPreventionFunctionalTest.php b/tests/ProxyManagerTest/Functional/FatalPreventionFunctionalTest.php index f2a7dbd3..8e4f48d6 100644 --- a/tests/ProxyManagerTest/Functional/FatalPreventionFunctionalTest.php +++ b/tests/ProxyManagerTest/Functional/FatalPreventionFunctionalTest.php @@ -4,9 +4,9 @@ namespace ProxyManagerTest\Functional; -use Laminas\Code\Generator\ClassGenerator; use PHPUnit\Framework\TestCase; use ProxyManager\Exception\ExceptionInterface; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\ProxyInterface; use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizerGenerator; diff --git a/tests/ProxyManagerTest/Functional/MultipleProxyGenerationTest.php b/tests/ProxyManagerTest/Functional/MultipleProxyGenerationTest.php index 5a8d223a..bccc7978 100644 --- a/tests/ProxyManagerTest/Functional/MultipleProxyGenerationTest.php +++ b/tests/ProxyManagerTest/Functional/MultipleProxyGenerationTest.php @@ -22,6 +22,7 @@ use ProxyManagerTestAsset\ClassWithMixedTypedProperties; use ProxyManagerTestAsset\ClassWithParentHint; use ProxyManagerTestAsset\ClassWithPhp80TypedMethods; +use ProxyManagerTestAsset\ClassWithPhp81Defaults; use ProxyManagerTestAsset\ClassWithPrivateProperties; use ProxyManagerTestAsset\ClassWithProtectedProperties; use ProxyManagerTestAsset\ClassWithPublicProperties; @@ -138,6 +139,10 @@ public function getTestedClasses(): array $objects[] = [new ClassWithPhp80TypedMethods()]; } + if (PHP_VERSION_ID >= 80100) { + $objects['php81defaults'] = [new ClassWithPhp81Defaults()]; + } + return $objects; } } diff --git a/tests/ProxyManagerTest/GeneratorStrategy/BaseGeneratorStrategyTest.php b/tests/ProxyManagerTest/GeneratorStrategy/BaseGeneratorStrategyTest.php index ac42d8e6..a9d110f7 100644 --- a/tests/ProxyManagerTest/GeneratorStrategy/BaseGeneratorStrategyTest.php +++ b/tests/ProxyManagerTest/GeneratorStrategy/BaseGeneratorStrategyTest.php @@ -4,8 +4,8 @@ namespace ProxyManagerTest\GeneratorStrategy; -use Laminas\Code\Generator\ClassGenerator; use PHPUnit\Framework\TestCase; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\Generator\Util\UniqueIdentifierGenerator; use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; diff --git a/tests/ProxyManagerTest/GeneratorStrategy/EvaluatingGeneratorStrategyTest.php b/tests/ProxyManagerTest/GeneratorStrategy/EvaluatingGeneratorStrategyTest.php index 5c617423..ca554a14 100644 --- a/tests/ProxyManagerTest/GeneratorStrategy/EvaluatingGeneratorStrategyTest.php +++ b/tests/ProxyManagerTest/GeneratorStrategy/EvaluatingGeneratorStrategyTest.php @@ -4,8 +4,8 @@ namespace ProxyManagerTest\GeneratorStrategy; -use Laminas\Code\Generator\ClassGenerator; use PHPUnit\Framework\TestCase; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\Generator\Util\UniqueIdentifierGenerator; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; diff --git a/tests/ProxyManagerTest/GeneratorStrategy/FileWriterGeneratorStrategyTest.php b/tests/ProxyManagerTest/GeneratorStrategy/FileWriterGeneratorStrategyTest.php index 092d6aa1..db394281 100644 --- a/tests/ProxyManagerTest/GeneratorStrategy/FileWriterGeneratorStrategyTest.php +++ b/tests/ProxyManagerTest/GeneratorStrategy/FileWriterGeneratorStrategyTest.php @@ -4,10 +4,10 @@ namespace ProxyManagerTest\GeneratorStrategy; -use Laminas\Code\Generator\ClassGenerator; use PHPUnit\Framework\TestCase; use ProxyManager\Exception\FileNotWritableException; use ProxyManager\FileLocator\FileLocatorInterface; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\Generator\Util\UniqueIdentifierGenerator; use ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy; diff --git a/tests/ProxyManagerTest/ProxyGenerator/AbstractProxyGeneratorTest.php b/tests/ProxyManagerTest/ProxyGenerator/AbstractProxyGeneratorTest.php index f1be1fff..22a70199 100644 --- a/tests/ProxyManagerTest/ProxyGenerator/AbstractProxyGeneratorTest.php +++ b/tests/ProxyManagerTest/ProxyGenerator/AbstractProxyGeneratorTest.php @@ -4,8 +4,8 @@ namespace ProxyManagerTest\ProxyGenerator; -use Laminas\Code\Generator\ClassGenerator; use PHPUnit\Framework\TestCase; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\Generator\Util\UniqueIdentifierGenerator; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; @@ -18,6 +18,7 @@ use ProxyManagerTestAsset\ClassWithMixedReferenceableTypedProperties; use ProxyManagerTestAsset\ClassWithMixedTypedProperties; use ProxyManagerTestAsset\ClassWithPhp80TypedMethods; +use ProxyManagerTestAsset\ClassWithPhp81Defaults; use ProxyManagerTestAsset\IterableMethodTypeHintedInterface; use ProxyManagerTestAsset\ObjectMethodTypeHintedInterface; use ProxyManagerTestAsset\ReturnTypeHintedClass; @@ -112,6 +113,10 @@ public function getTestedImplementations(): array $implementations[] = [ClassWithPhp80TypedMethods::class]; } + if (PHP_VERSION_ID >= 80100) { + $implementations['php81defaults'] = [ClassWithPhp81Defaults::class]; + } + return $implementations; } } diff --git a/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizerTest.php b/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizerTest.php index 45ef998f..d830bb89 100644 --- a/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizerTest.php +++ b/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizerTest.php @@ -4,9 +4,9 @@ namespace ProxyManagerTest\ProxyGenerator; -use Laminas\Code\Generator\ClassGenerator; use ProxyManager\Exception\InvalidProxiedClassException; use ProxyManager\Exception\UnsupportedProxiedClassException; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\Proxy\AccessInterceptorInterface; use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizerGenerator; use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; diff --git a/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhostGeneratorTest.php b/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhostGeneratorTest.php index d7bfa5e5..97e21d68 100644 --- a/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhostGeneratorTest.php +++ b/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhostGeneratorTest.php @@ -4,8 +4,8 @@ namespace ProxyManagerTest\ProxyGenerator; -use Laminas\Code\Generator\ClassGenerator; use ProxyManager\Exception\InvalidProxiedClassException; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\Proxy\GhostObjectInterface; use ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator; use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; diff --git a/tests/ProxyManagerTest/ProxyGenerator/NullObjectGeneratorTest.php b/tests/ProxyManagerTest/ProxyGenerator/NullObjectGeneratorTest.php index 86d91a0d..0fd72171 100644 --- a/tests/ProxyManagerTest/ProxyGenerator/NullObjectGeneratorTest.php +++ b/tests/ProxyManagerTest/ProxyGenerator/NullObjectGeneratorTest.php @@ -4,7 +4,7 @@ namespace ProxyManagerTest\ProxyGenerator; -use Laminas\Code\Generator\ClassGenerator; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\Generator\Util\UniqueIdentifierGenerator; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\NullObjectInterface; @@ -19,6 +19,7 @@ use ProxyManagerTestAsset\ClassWithMixedReferenceableTypedProperties; use ProxyManagerTestAsset\ClassWithMixedTypedProperties; use ProxyManagerTestAsset\ClassWithPhp80TypedMethods; +use ProxyManagerTestAsset\ClassWithPhp81Defaults; use ReflectionClass; use ReflectionMethod; @@ -131,6 +132,10 @@ public function getTestedImplementations(): array $implementations[] = [ClassWithPhp80TypedMethods::class]; } + if (PHP_VERSION_ID >= 80100) { + $implementations[] = [ClassWithPhp81Defaults::class]; + } + return $implementations; } } diff --git a/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethodTest.php b/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethodTest.php index 7eb99087..16d4ad83 100644 --- a/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethodTest.php +++ b/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethodTest.php @@ -40,13 +40,14 @@ public function testBodyStructureWithParameters(): void self::assertSame('publicByReferenceParameterMethod', $method->getName()); self::assertCount(2, $method->getParameters()); self::assertSame( - '$defaultValues = array ( - 0 => NULL, - 1 => NULL, -); -$declaredParameterCount = 2; + 'static $reflection; -$args = \func_get_args() + $defaultValues; +$args = \func_get_args(); + +switch (\func_num_args()) { + case 0: $args[] = NULL; + case 1: $args[] = NULL; +} $return = $this->adapter->call(\'Laminas\\\\Code\\\\Generator\\\\PropertyGenerator\', \'publicByReferenceParameterMethod\', $args); @@ -74,12 +75,13 @@ public function testBodyStructureWithArrayParameter(): void self::assertSame('publicArrayHintedMethod', $method->getName()); self::assertCount(1, $method->getParameters()); self::assertSame( - "\$defaultValues = array ( - 0 => NULL, -); -\$declaredParameterCount = 1; + "static \$reflection; + +\$args = \\func_get_args(); -\$args = \\func_get_args() + \$defaultValues; +switch (\\func_num_args()) { + case 0: \$args[] = NULL; +} \$return = \$this->adapter->call('Laminas\\\\Code\\\\Generator\\\\PropertyGenerator', 'publicArrayHintedMethod', \$args); @@ -107,11 +109,12 @@ public function testBodyStructureWithoutParameters(): void self::assertSame('publicMethod', $method->getName()); self::assertCount(0, $method->getParameters()); self::assertSame( - "\$defaultValues = array ( -); -\$declaredParameterCount = 0; + "static \$reflection; + +\$args = \\func_get_args(); -\$args = \\func_get_args() + \$defaultValues; +switch (\\func_num_args()) { +} \$return = \$this->adapter->call('Laminas\\\\Code\\\\Generator\\\\PropertyGenerator', 'publicMethod', \$args); diff --git a/tests/ProxyManagerTest/ProxyGenerator/RemoteObjectGeneratorTest.php b/tests/ProxyManagerTest/ProxyGenerator/RemoteObjectGeneratorTest.php index 9700a024..3eedc968 100644 --- a/tests/ProxyManagerTest/ProxyGenerator/RemoteObjectGeneratorTest.php +++ b/tests/ProxyManagerTest/ProxyGenerator/RemoteObjectGeneratorTest.php @@ -4,7 +4,7 @@ namespace ProxyManagerTest\ProxyGenerator; -use Laminas\Code\Generator\ClassGenerator; +use ProxyManager\Generator\ClassGenerator; use ProxyManager\Generator\Util\UniqueIdentifierGenerator; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\RemoteObjectInterface; @@ -18,6 +18,7 @@ use ProxyManagerTestAsset\ClassWithMixedReferenceableTypedProperties; use ProxyManagerTestAsset\ClassWithMixedTypedProperties; use ProxyManagerTestAsset\ClassWithPhp80TypedMethods; +use ProxyManagerTestAsset\ClassWithPhp81Defaults; use ReflectionClass; use function array_diff; @@ -101,6 +102,10 @@ public function getTestedImplementations(): array $implementations[] = [ClassWithPhp80TypedMethods::class]; } + if (PHP_VERSION_ID >= 80100) { + $implementations['php81defaults'] = [ClassWithPhp81Defaults::class]; + } + return $implementations; } } diff --git a/tests/ProxyManagerTestAsset/ClassWithPhp81Defaults.php b/tests/ProxyManagerTestAsset/ClassWithPhp81Defaults.php new file mode 100644 index 00000000..c4fd1e9f --- /dev/null +++ b/tests/ProxyManagerTestAsset/ClassWithPhp81Defaults.php @@ -0,0 +1,24 @@ +