From 35bd9ff96b8f486b9bf494cc0a55efe1188c7aea Mon Sep 17 00:00:00 2001 From: tsuchiya Date: Sat, 19 Feb 2022 12:12:32 +0900 Subject: [PATCH 1/8] Add red test --- tests/CompilerTest.php | 12 ++++++++++++ tests/Fake/FakeTypedMock.php | 19 +++++++++++++++++++ tests/Fake/FakeTypedMockChild.php | 9 +++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/Fake/FakeTypedMock.php create mode 100644 tests/Fake/FakeTypedMockChild.php diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index 9a56c66a..2015bd9f 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -4,6 +4,7 @@ namespace Ray\Aop; +use ArrayIterator; use Doctrine\Common\Annotations\AnnotationReader; use FakeGlobalEmptyNamespaced; use FakeGlobalNamespaced; @@ -130,6 +131,17 @@ public function testParentMethodIntercept(): void $this->assertSame(2, $result); } + public function testTypedParentMethodIntercept(): void + { + $bind = (new Bind())->bindInterceptors('passIterator', [new NullInterceptor()]); + $mock = $this->compiler->newInstance(FakeTypedMockChild::class, [], $bind); + assert($mock instanceof FakeTypedMockChild); + assert(property_exists($mock, 'bindings')); + $mock->bindings = $this->bind->getBindings(); + $result = $mock->passIterator(new ArrayIterator()); + $this->assertInstanceOf(ArrayIterator::class, $result); + } + public function testParentOfParentMethodIntercept(): void { $mock = $this->compiler->newInstance(FakeMockChildChild::class, [], $this->bind); diff --git a/tests/Fake/FakeTypedMock.php b/tests/Fake/FakeTypedMock.php new file mode 100644 index 00000000..2870a323 --- /dev/null +++ b/tests/Fake/FakeTypedMock.php @@ -0,0 +1,19 @@ + Date: Sat, 19 Feb 2022 16:32:27 +0900 Subject: [PATCH 2/8] Update Test --- tests/CompilerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index 2015bd9f..151505f9 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -137,7 +137,7 @@ public function testTypedParentMethodIntercept(): void $mock = $this->compiler->newInstance(FakeTypedMockChild::class, [], $bind); assert($mock instanceof FakeTypedMockChild); assert(property_exists($mock, 'bindings')); - $mock->bindings = $this->bind->getBindings(); + $mock->bindings = $bind->getBindings(); $result = $mock->passIterator(new ArrayIterator()); $this->assertInstanceOf(ArrayIterator::class, $result); } From 9d1e8e300a0df97739b4ec66094a986ba15f405e Mon Sep 17 00:00:00 2001 From: tsuchiya Date: Sat, 19 Feb 2022 16:35:50 +0900 Subject: [PATCH 3/8] Use NameResolver for including namespace --- src/VisitorFactory.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/VisitorFactory.php b/src/VisitorFactory.php index a41d6864..becdd88b 100644 --- a/src/VisitorFactory.php +++ b/src/VisitorFactory.php @@ -5,6 +5,7 @@ namespace Ray\Aop; use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; use PhpParser\Parser; use Ray\Aop\Exception\InvalidSourceClassException; use ReflectionClass; @@ -31,7 +32,9 @@ public function __construct(Parser $parser) public function __invoke(ReflectionClass $class): CodeVisitor { $traverser = new NodeTraverser(); + $nameResolver = new NameResolver(); $visitor = new CodeVisitor(); + $traverser->addVisitor($nameResolver); $traverser->addVisitor($visitor); $fileName = $class->getFileName(); if (is_bool($fileName)) { From 1033ebb93f61bfb82078b071771ee2e5aad9759b Mon Sep 17 00:00:00 2001 From: tsuchiya Date: Sat, 19 Feb 2022 16:36:23 +0900 Subject: [PATCH 4/8] Update CodeGenPhp71Test.php --- tests/CodeGenPhp71Test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CodeGenPhp71Test.php b/tests/CodeGenPhp71Test.php index c7e9df03..33742011 100644 --- a/tests/CodeGenPhp71Test.php +++ b/tests/CodeGenPhp71Test.php @@ -77,7 +77,7 @@ public function testTypedParam(): void $bind = new Bind(); $bind->bindInterceptors('typed', []); $code = $this->codeGen->generate(new ReflectionClass(FakePhp71NullableClass::class), $bind); - $expected = 'public function typed(SplObjectStorage $storage)'; + $expected = 'public function typed(\SplObjectStorage $storage)'; $this->assertStringContainsString($expected, $code->code); } @@ -86,7 +86,7 @@ public function testUseTyped(): void $bind = new Bind(); $bind->bindInterceptors('useTyped', []); $code = $this->codeGen->generate(new ReflectionClass(FakePhp71NullableClass::class), $bind); - $expected = 'public function useTyped(CodeGen $codeGen)'; + $expected = 'public function useTyped(\Ray\Aop\CodeGen $codeGen)'; $this->assertStringContainsString($expected, $code->code); } } From 65b8aba9641ebd3afedbe8d9c104bc6d5a87e499 Mon Sep 17 00:00:00 2001 From: tsuchiya Date: Sat, 19 Feb 2022 16:39:08 +0900 Subject: [PATCH 5/8] Update test case --- tests/CodeGenPhp71Test.php | 11 ++++++++++- tests/Fake/FakePhp71NullableClass.php | 9 ++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/CodeGenPhp71Test.php b/tests/CodeGenPhp71Test.php index 33742011..d3036396 100644 --- a/tests/CodeGenPhp71Test.php +++ b/tests/CodeGenPhp71Test.php @@ -77,7 +77,7 @@ public function testTypedParam(): void $bind = new Bind(); $bind->bindInterceptors('typed', []); $code = $this->codeGen->generate(new ReflectionClass(FakePhp71NullableClass::class), $bind); - $expected = 'public function typed(\SplObjectStorage $storage)'; + $expected = 'public function typed(\SplObjectStorage $storage) : \SplObjectStorage'; $this->assertStringContainsString($expected, $code->code); } @@ -89,4 +89,13 @@ public function testUseTyped(): void $expected = 'public function useTyped(\Ray\Aop\CodeGen $codeGen)'; $this->assertStringContainsString($expected, $code->code); } + + public function testAttribute(): void + { + $bind = new Bind(); + $bind->bindInterceptors('attributed', []); + $code = $this->codeGen->generate(new ReflectionClass(FakePhp71NullableClass::class), $bind); + $expected = '#[\Ray\Aop\Annotation\FakeMarker3]'; + $this->assertStringContainsString($expected, $code->code); + } } diff --git a/tests/Fake/FakePhp71NullableClass.php b/tests/Fake/FakePhp71NullableClass.php index bd0e0d53..03e67d4b 100644 --- a/tests/Fake/FakePhp71NullableClass.php +++ b/tests/Fake/FakePhp71NullableClass.php @@ -5,6 +5,7 @@ namespace Ray\Aop; use Composer\Autoload; +use Ray\Aop\Annotation\FakeMarker3; use SplObjectStorage; class FakePhp71NullableClass @@ -34,11 +35,17 @@ public function variadicParam(int ...$ids) return $ids[0]; } - public function typed(SplObjectStorage $storage) + public function typed(SplObjectStorage $storage): SplObjectStorage { + return $storage; } public function useTyped(CodeGen $codeGen) { } + + #[FakeMarker3] + public function attributed() + { + } } From afc7f52d088aecc067c99dbbdb0087abc842f930 Mon Sep 17 00:00:00 2001 From: tsuchiya Date: Mon, 21 Feb 2022 20:50:50 +0900 Subject: [PATCH 6/8] Solve annotation namespace --- src/AopClass.php | 2 +- src/AopProps.php | 12 +++++++----- src/CodeVisitor.php | 19 ++++++++++++++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/AopClass.php b/src/AopClass.php index 6acbbbfa..092ce008 100644 --- a/src/AopClass.php +++ b/src/AopClass.php @@ -45,7 +45,7 @@ public function __invoke(CodeVisitor $visitor, ReflectionClass $sourceClass, Bin { assert($visitor->class instanceof Class_); $methods = $this->codeGenMethod->getMethods($sourceClass, $bind, $visitor); - $propStms = ($this->aopProps)($sourceClass); + $propStms = ($this->aopProps)($sourceClass, $visitor); $classStm = $visitor->class; $newClassName = ($this->aopClassName)((string) $visitor->class->name, $bind->toString('')); $classStm->name = new Identifier($newClassName); diff --git a/src/AopProps.php b/src/AopProps.php index b9931646..ea9f61b3 100644 --- a/src/AopProps.php +++ b/src/AopProps.php @@ -31,7 +31,7 @@ public function __construct(BuilderFactory $factory) * * @return Property[] */ - public function __invoke(ReflectionClass $class): array + public function __invoke(ReflectionClass $class, CodeVisitor $visitor): array { $pros = []; $pros[] = $this->factory @@ -47,12 +47,12 @@ public function __invoke(ReflectionClass $class): array $pros[] = $this->factory ->property('methodAnnotations') - ->setDefault($this->getMethodAnnotations($class)) + ->setDefault($this->getMethodAnnotations($class, $visitor)) ->makePublic() ->getNode(); $pros[] = $this->factory ->property('classAnnotations') - ->setDefault($this->getClassAnnotation($class)) + ->setDefault($this->getClassAnnotation($class, $visitor)) ->makePublic() ->getNode(); $pros[] = $this->factory @@ -67,7 +67,7 @@ public function __invoke(ReflectionClass $class): array /** * @param ReflectionClass $class */ - private function getMethodAnnotations(ReflectionClass $class): string + private function getMethodAnnotations(ReflectionClass $class, CodeVisitor $visitor): string { $methodsAnnotation = []; $methods = $class->getMethods(); @@ -78,6 +78,7 @@ private function getMethodAnnotations(ReflectionClass $class): string } $methodsAnnotation[$method->name] = $annotations; + $visitor->addUses($annotations); } return serialize($methodsAnnotation); @@ -88,9 +89,10 @@ private function getMethodAnnotations(ReflectionClass $class): string * * @template T of object */ - private function getClassAnnotation(ReflectionClass $class): string + private function getClassAnnotation(ReflectionClass $class, CodeVisitor $visitor): string { $classAnnotations = $this->reader->getClassAnnotations($class); + $visitor->addUses($classAnnotations); return serialize($classAnnotations); } diff --git a/src/CodeVisitor.php b/src/CodeVisitor.php index a0a28da5..2c5f12f0 100644 --- a/src/CodeVisitor.php +++ b/src/CodeVisitor.php @@ -13,6 +13,9 @@ use PhpParser\NodeVisitorAbstract; use Ray\Aop\Exception\MultipleClassInOneFileException; +use function get_class; +use function implode; + final class CodeVisitor extends NodeVisitorAbstract { /** @var ?Namespace_ */ @@ -42,7 +45,7 @@ public function enterNode(Node $node) } if ($node instanceof Use_) { - $this->use[] = $node; + $this->addUse($node); return null; } @@ -84,4 +87,18 @@ private function enterNodeClass(Node $node) return null; } + + /** @param array $annotations */ + public function addUses(array $annotations): void + { + foreach ($annotations as $annotation) { + $this->addUse(new Use_([new Node\Stmt\UseUse(new Node\Name(get_class($annotation)))])); + } + } + + private function addUse(Use_ $use): void + { + $index = implode('\\', $use->uses[0]->name->parts); + $this->use[$index] = $use; + } } From 833f6bdb15b2cf4cb08a2cba77d27ffdd67e3df2 Mon Sep 17 00:00:00 2001 From: tsuchiya Date: Mon, 21 Feb 2022 20:55:16 +0900 Subject: [PATCH 7/8] Add test case --- tests/CodeGenPhp71Test.php | 9 +++++++++ tests/Fake/FakePhp71NullableClass.php | 1 + 2 files changed, 10 insertions(+) diff --git a/tests/CodeGenPhp71Test.php b/tests/CodeGenPhp71Test.php index d3036396..e860d4bd 100644 --- a/tests/CodeGenPhp71Test.php +++ b/tests/CodeGenPhp71Test.php @@ -98,4 +98,13 @@ public function testAttribute(): void $expected = '#[\Ray\Aop\Annotation\FakeMarker3]'; $this->assertStringContainsString($expected, $code->code); } + + public function testUseAnnotation(): void + { + $bind = new Bind(); + $bind->bindInterceptors('attributed', []); + $code = $this->codeGen->generate(new ReflectionClass(FakePhp71NullableClass::class), $bind); + $expected = "use Ray\\Aop\\Annotation\\FakeMarker3;\n"; + $this->assertStringContainsString($expected, $code->code); + } } diff --git a/tests/Fake/FakePhp71NullableClass.php b/tests/Fake/FakePhp71NullableClass.php index 03e67d4b..d5d273b3 100644 --- a/tests/Fake/FakePhp71NullableClass.php +++ b/tests/Fake/FakePhp71NullableClass.php @@ -44,6 +44,7 @@ public function useTyped(CodeGen $codeGen) { } + /** @FakeMarker3 */ #[FakeMarker3] public function attributed() { From 151e3b96fd65147446cfec2ca10e7ada1bde7eb1 Mon Sep 17 00:00:00 2001 From: tsuchiya Date: Mon, 21 Feb 2022 21:04:47 +0900 Subject: [PATCH 8/8] Fix namespace --- src/CodeVisitor.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CodeVisitor.php b/src/CodeVisitor.php index 2c5f12f0..616d34ca 100644 --- a/src/CodeVisitor.php +++ b/src/CodeVisitor.php @@ -5,11 +5,13 @@ namespace Ray\Aop; use PhpParser\Node; +use PhpParser\Node\Name; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Declare_; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Use_; +use PhpParser\Node\Stmt\UseUse; use PhpParser\NodeVisitorAbstract; use Ray\Aop\Exception\MultipleClassInOneFileException; @@ -92,7 +94,7 @@ private function enterNodeClass(Node $node) public function addUses(array $annotations): void { foreach ($annotations as $annotation) { - $this->addUse(new Use_([new Node\Stmt\UseUse(new Node\Name(get_class($annotation)))])); + $this->addUse(new Use_([new UseUse(new Name(get_class($annotation)))])); } }