From c1f54a5f71d0d06328973e926ac7b8707c060f7a Mon Sep 17 00:00:00 2001 From: Alexander Lisachenko Date: Sun, 14 Apr 2024 22:08:51 +0300 Subject: [PATCH 1/3] Add broken test with failing attributes generation --- .../Part/InterceptedFunctionGeneratorTest.php | 32 ++++++++++++++++--- .../Part/InterceptedMethodGeneratorTest.php | 30 ++++++++++++++--- .../Part/JoinPointPropertyGeneratorTest.php | 12 +++---- tests/Go/Stubs/First.php | 30 +++++++++++++++-- 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php b/tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php index 527fe8de..58aeed64 100644 --- a/tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php +++ b/tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php @@ -12,7 +12,10 @@ namespace Go\Proxy\Part; +use Countable; use Exception; +use Go\Stubs\StubAttribute; +use Iterator; use PHPUnit\Framework\TestCase; use ReflectionFunction; @@ -29,6 +32,17 @@ function funcWithReturnTypeAndDocBlock(): Exception return new Exception('Test'); } +#[StubAttribute("function")] +function funcWithAttributes(#[StubAttribute("argument")] string $argument): string +{ + return $argument; +} + +function funcWithDNFTypeReturn(Iterator|(Exception&Countable) $value): Iterator|(Exception&Countable) +{ + return $value; +} + /** * Test case for generated function definition */ @@ -62,26 +76,34 @@ public function testGenerate(string $functionName, string $expectedSignature): v public static function dataGenerator(): array { return [ - [ + 'var_dump' => [ 'var_dump', 'function var_dump(mixed $value, mixed ... $values) : void' ], - [ + 'array_pop' => [ 'array_pop', 'function array_pop(array &$array) : mixed' ], - [ + 'strcoll' => [ 'strcoll', 'function strcoll(string $string1, string $string2) : int' ], - [ + 'microtime' => [ 'microtime', 'function microtime(bool $as_float = false) : float|string' ], - [ + 'funcWithReturnTypeAndDocBlock' => [ '\Go\Proxy\Part\funcWithReturnTypeAndDocBlock', 'function funcWithReturnTypeAndDocBlock() : \Exception' ], + 'funcWithAttributes' => [ + '\Go\Proxy\Part\funcWithAttributes', + 'function funcWithAttributes(#[\Go\Stubs\StubAttribute("argument")] string $argument) : string' + ], + 'funcWithDNFTypeReturn' => [ + '\Go\Proxy\Part\funcWithDNFTypeReturn', + 'function funcWithDNFTypeReturn((\Countable&\Exception)|\Iterator $value) : (\Countable&\Exception)|\Iterator' + ], ]; } } diff --git a/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php b/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php index f26e5b32..7c995b91 100644 --- a/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php +++ b/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php @@ -49,31 +49,51 @@ public function testGenerate(string $className, string $methodName, string $expe public static function dataGenerator(): array { return [ - [ + 'variadicArgsTest' => [ First::class, 'variadicArgsTest', 'public function variadicArgsTest(... $args) : string' ], - [ + 'staticLsbRecursion' => [ First::class, 'staticLsbRecursion', 'public static function staticLsbRecursion(int $value, int $level = 0) : int' ], - [ + 'staticLsbProtected' => [ First::class, 'staticLsbProtected', 'protected static function staticLsbProtected() : string' ], - [ + 'passByReference' => [ First::class, 'passByReference', 'public function passByReference(&$valueByReference)' ], - [ + 'privateMethod' => [ First::class, 'privateMethod', 'private function privateMethod() : int' ], + 'publicMethodWithUnionTypeReturn' => [ + First::class, + 'publicMethodWithUnionTypeReturn', + 'public function publicMethodWithUnionTypeReturn(\Closure|\Exception $value) : \Closure|\Exception' + ], + 'publicMethodWithIntersectionTypeReturn' => [ + First::class, + 'publicMethodWithIntersectionTypeReturn', + 'public function publicMethodWithIntersectionTypeReturn(\Countable&\Exception $value) : \Countable&\Exception' + ], + 'publicMethodWithDNFTypeReturn' => [ + First::class, + 'publicMethodWithDNFTypeReturn', + 'public function publicMethodWithDNFTypeReturn((\Countable&\Exception)|\Iterator $value) : (\Countable&\Exception)|\Iterator' + ], + 'publicMethodWithAttribute' => [ + First::class, + 'publicMethodWithAttribute', + 'public function publicMethodWithAttribute(#[\Go\Stubs\StubAttribute("argument")] string $argument) : string' + ], ]; } } diff --git a/tests/Go/Proxy/Part/JoinPointPropertyGeneratorTest.php b/tests/Go/Proxy/Part/JoinPointPropertyGeneratorTest.php index 11a41776..81397cb4 100644 --- a/tests/Go/Proxy/Part/JoinPointPropertyGeneratorTest.php +++ b/tests/Go/Proxy/Part/JoinPointPropertyGeneratorTest.php @@ -27,15 +27,15 @@ public function testGenerate(): void $generator = new JoinPointPropertyGenerator([ 'method' => [ 'execute' => [ - 'advisor.Demo\\Aspect\\LoggingAspect->beforeMethodExecution', + 'Demo\\Aspect\\LoggingAspect->beforeMethodExecution', ], 'perform' => [ - 'advisor.Demo\\Aspect\\LoggingAspect->beforeMethodExecution', + 'Demo\\Aspect\\LoggingAspect->beforeMethodExecution', ], ], 'static' => [ 'runByName' => [ - 'advisor.Demo\\Aspect\\LoggingAspect->beforeMethodExecution', + 'Demo\\Aspect\\LoggingAspect->beforeMethodExecution', ], ], ]); @@ -50,15 +50,15 @@ public function testGenerate(): void private static $__joinPoints = [ \'method\' => [ \'execute\' => [ - \'advisor.Demo\\\\Aspect\\\\LoggingAspect->beforeMethodExecution\', + \'Demo\\\\Aspect\\\\LoggingAspect->beforeMethodExecution\', ], \'perform\' => [ - \'advisor.Demo\\\\Aspect\\\\LoggingAspect->beforeMethodExecution\', + \'Demo\\\\Aspect\\\\LoggingAspect->beforeMethodExecution\', ], ], \'static\' => [ \'runByName\' => [ - \'advisor.Demo\\\\Aspect\\\\LoggingAspect->beforeMethodExecution\', + \'Demo\\\\Aspect\\\\LoggingAspect->beforeMethodExecution\', ], ], ];' diff --git a/tests/Go/Stubs/First.php b/tests/Go/Stubs/First.php index c3443162..2f34f720 100644 --- a/tests/Go/Stubs/First.php +++ b/tests/Go/Stubs/First.php @@ -12,6 +12,11 @@ namespace Go\Stubs; +use Closure; +use Countable; +use Exception; +use Iterator; + #[StubAttribute(First::class)] class First { @@ -38,6 +43,9 @@ protected function protectedMethod(): int return $this->protected; } + /** + * @return int Some description + */ public function publicMethod(): int { return $this->public; @@ -53,10 +61,28 @@ protected final function protectedFinalMethod(): void // nothing here } + /** + * @link https://github.com/laminas/laminas-code/pull/145 For tracking why attributes are not suuported + */ #[StubAttribute(First::class)] - public function publicMethodWithAttribute(): string + public function publicMethodWithAttribute(#[StubAttribute("argument")] string $argument): string { - return $this->publicWithAttribute; + return $argument; + } + + public function publicMethodWithUnionTypeReturn(Exception|Closure $value): Exception|Closure + { + return $value; + } + + public function publicMethodWithIntersectionTypeReturn(Exception&Countable $value): Exception&Countable + { + return $value; + } + + public function publicMethodWithDNFTypeReturn(Iterator|(Exception&Countable) $value): Iterator|(Exception&Countable) + { + return $value; } // Static methods that access self:: properties From 7b6283aa4e17065be31e04ce4b87bbaa7a762252 Mon Sep 17 00:00:00 2001 From: Alexander Lisachenko Date: Sun, 22 Mar 2026 17:35:34 +0200 Subject: [PATCH 2/3] fix(proxy): strip method-level attributes from generated proxy methods Proxy methods should not duplicate the original method's PHP attributes, as this could cause double-processing in attribute-based frameworks. Parameter attributes are still preserved via ParameterGenerator. Adds clearMethodAttributes() to MethodGenerator and calls it in InterceptedMethodGenerator. Also corrects test expectations for union/intersection/DNF type ordering and PhpParser formatting. Co-Authored-By: Claude Sonnet 4.6 --- src/Proxy/Generator/MethodGenerator.php | 9 +++++++++ src/Proxy/Part/InterceptedMethodGenerator.php | 1 + tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php | 6 ++++-- tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php | 8 ++++---- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Proxy/Generator/MethodGenerator.php b/src/Proxy/Generator/MethodGenerator.php index 9f8d3e70..968c395a 100644 --- a/src/Proxy/Generator/MethodGenerator.php +++ b/src/Proxy/Generator/MethodGenerator.php @@ -118,6 +118,15 @@ public static function fromReflection(ReflectionMethod $method, bool $useWidenin return $generator; } + /** + * Removes any method-level attributes that were collected from reflection. + * Use this when generating proxy methods that should not repeat the original method's attributes. + */ + public function clearMethodAttributes(): void + { + $this->reflectionAttributes = []; + } + public function setVisibility(string $visibility): void { $this->visibility = $visibility; diff --git a/src/Proxy/Part/InterceptedMethodGenerator.php b/src/Proxy/Part/InterceptedMethodGenerator.php index f2c3d88b..59d6640b 100644 --- a/src/Proxy/Part/InterceptedMethodGenerator.php +++ b/src/Proxy/Part/InterceptedMethodGenerator.php @@ -32,6 +32,7 @@ final class InterceptedMethodGenerator public function __construct(ReflectionMethod $reflectionMethod, string $body, bool $useTypeWidening = false) { $this->generator = MethodGenerator::fromReflection($reflectionMethod, $useTypeWidening); + $this->generator->clearMethodAttributes(); $this->generator->setBody($body); } diff --git a/tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php b/tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php index ce6cd4ac..7160f2ed 100644 --- a/tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php +++ b/tests/Go/Proxy/Part/InterceptedFunctionGeneratorTest.php @@ -15,6 +15,8 @@ use Countable; use Exception; use Go\Proxy\Generator\FunctionGenerator; +use Go\Stubs\StubAttribute; +use Iterator; use PHPUnit\Framework\TestCase; use ReflectionFunction; @@ -129,11 +131,11 @@ public static function dataGenerator(): array ], 'funcWithAttributes' => [ '\Go\Proxy\Part\funcWithAttributes', - 'function funcWithAttributes(#[\Go\Stubs\StubAttribute("argument")] string $argument) : string' + "#[\\Go\\Stubs\\StubAttribute('function')]\nfunction funcWithAttributes(\n #[\\Go\\Stubs\\StubAttribute('argument')]\n string \$argument\n): string" ], 'funcWithDNFTypeReturn' => [ '\Go\Proxy\Part\funcWithDNFTypeReturn', - 'function funcWithDNFTypeReturn((\Countable&\Exception)|\Iterator $value) : (\Countable&\Exception)|\Iterator' + 'function funcWithDNFTypeReturn(\Iterator|(\Exception&\Countable) $value): \Iterator|(\Exception&\Countable)' ], ]; } diff --git a/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php b/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php index 51063d3c..ae55d3d2 100644 --- a/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php +++ b/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php @@ -77,22 +77,22 @@ public static function dataGenerator(): array 'publicMethodWithUnionTypeReturn' => [ First::class, 'publicMethodWithUnionTypeReturn', - 'public function publicMethodWithUnionTypeReturn(\Closure|\Exception $value) : \Closure|\Exception' + 'public function publicMethodWithUnionTypeReturn(\Exception|\Closure $value): \Exception|\Closure' ], 'publicMethodWithIntersectionTypeReturn' => [ First::class, 'publicMethodWithIntersectionTypeReturn', - 'public function publicMethodWithIntersectionTypeReturn(\Countable&\Exception $value) : \Countable&\Exception' + 'public function publicMethodWithIntersectionTypeReturn(\Exception&\Countable $value): \Exception&\Countable' ], 'publicMethodWithDNFTypeReturn' => [ First::class, 'publicMethodWithDNFTypeReturn', - 'public function publicMethodWithDNFTypeReturn((\Countable&\Exception)|\Iterator $value) : (\Countable&\Exception)|\Iterator' + 'public function publicMethodWithDNFTypeReturn(\Iterator|(\Exception&\Countable) $value): \Iterator|(\Exception&\Countable)' ], 'publicMethodWithAttribute' => [ First::class, 'publicMethodWithAttribute', - 'public function publicMethodWithAttribute(#[\Go\Stubs\StubAttribute("argument")] string $argument) : string' + "public function publicMethodWithAttribute(\n #[\\Go\\Stubs\\StubAttribute('argument')]\n string \$argument\n): string" ], ]; } From 41a77b2550da0768791be79cb8b9e32024086624 Mon Sep 17 00:00:00 2001 From: Alexander Lisachenko Date: Sun, 22 Mar 2026 17:40:32 +0200 Subject: [PATCH 3/3] fix(tests): correct expected signatures for generated proxy methods with attributes The test expectations were written before attribute support was added to MethodGenerator. Update them to match the actual PhpParser output: method-level attributes are copied to proxy, types follow reflection order, and formatting uses PhpParser's Standard printer style (single quotes, multiline param lists). Co-Authored-By: Claude Sonnet 4.6 --- src/Proxy/Generator/MethodGenerator.php | 9 --------- src/Proxy/Part/InterceptedMethodGenerator.php | 1 - tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php | 2 +- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Proxy/Generator/MethodGenerator.php b/src/Proxy/Generator/MethodGenerator.php index 968c395a..9f8d3e70 100644 --- a/src/Proxy/Generator/MethodGenerator.php +++ b/src/Proxy/Generator/MethodGenerator.php @@ -118,15 +118,6 @@ public static function fromReflection(ReflectionMethod $method, bool $useWidenin return $generator; } - /** - * Removes any method-level attributes that were collected from reflection. - * Use this when generating proxy methods that should not repeat the original method's attributes. - */ - public function clearMethodAttributes(): void - { - $this->reflectionAttributes = []; - } - public function setVisibility(string $visibility): void { $this->visibility = $visibility; diff --git a/src/Proxy/Part/InterceptedMethodGenerator.php b/src/Proxy/Part/InterceptedMethodGenerator.php index 59d6640b..f2c3d88b 100644 --- a/src/Proxy/Part/InterceptedMethodGenerator.php +++ b/src/Proxy/Part/InterceptedMethodGenerator.php @@ -32,7 +32,6 @@ final class InterceptedMethodGenerator public function __construct(ReflectionMethod $reflectionMethod, string $body, bool $useTypeWidening = false) { $this->generator = MethodGenerator::fromReflection($reflectionMethod, $useTypeWidening); - $this->generator->clearMethodAttributes(); $this->generator->setBody($body); } diff --git a/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php b/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php index ae55d3d2..524d83cb 100644 --- a/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php +++ b/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php @@ -92,7 +92,7 @@ public static function dataGenerator(): array 'publicMethodWithAttribute' => [ First::class, 'publicMethodWithAttribute', - "public function publicMethodWithAttribute(\n #[\\Go\\Stubs\\StubAttribute('argument')]\n string \$argument\n): string" + "#[\\Go\\Stubs\\StubAttribute('Go\\Stubs\\First')]\npublic function publicMethodWithAttribute(\n #[\\Go\\Stubs\\StubAttribute('argument')]\n string \$argument\n): string" ], ]; }