diff --git a/.gitignore b/.gitignore index e9f9cb43..552e45a6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ vendor cache.properties build composer.phar -docs/demo/tmp/* -tests/tmp/* +composer.lock +tmp diff --git a/.php_cs b/.php_cs new file mode 100644 index 00000000..141f75bd --- /dev/null +++ b/.php_cs @@ -0,0 +1,20 @@ +level(null); +$config->fixers( + array( + 'indentation', + 'linefeed', + 'trailing_spaces', + 'short_tag', + 'visibility', + 'php_closing_tag', + 'braces', + 'function_declaration', + 'psr0', + 'elseif', + 'eof_ending', + 'unused_use', + ) +); +return $config; diff --git a/README.md b/README.md index 3d8ddd29..7b154aef 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -Aspect Oriented Framework for PHP -======= +# Ray.Aop + +## Aspect Oriented Framework for PHP [![Latest Stable Version](https://poser.pugx.org/ray/aop/v/stable.png)](https://packagist.org/packages/ray/aop) -[![Build Status](https://secure.travis-ci.org/koriym/Ray.Aop.png?branch=master)](http://travis-ci.org/koriym/Ray.Aop) -[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/koriym/Ray.Aop/badges/quality-score.png?s=bb5414751b994336b6310caf61029ac09b907779)](https://scrutinizer-ci.com/g/koriym/Ray.Aop/) +[![Latest Unstable Version](http://img.shields.io/badge/unstable-~2.0%40dev-green.svg)](https://packagist.org/packages/ray/aop) +[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/koriym/Ray.Aop/badges/quality-score.png?develop-2)](https://scrutinizer-ci.com/g/koriym/Ray.Aop/) [![Code Coverage](https://scrutinizer-ci.com/g/koriym/Ray.Aop/badges/coverage.png?s=5604fdfae48a5a31242d3e46018515e2f30083d7)](https://scrutinizer-ci.com/g/koriym/Ray.Aop/) +[![Build Status](https://secure.travis-ci.org/koriym/Ray.Aop.png?branch=develop-2)](http://travis-ci.org/koriym/Ray.Aop) [[Japanese]](https://github.com/koriym/Ray.Aop/blob/develop/README.ja.md) diff --git a/composer.json b/composer.json index a9c41d4b..c47dcf27 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,11 @@ "autoload": { "psr-4": { "Ray\\Aop\\": "src/" } }, + "autoload-dev": { + "psr-4": { "Ray\\Aop\\": "tests/" }, + "psr-4": { "Ray\\Aop\\": "tests/Fake/" }, + "files": ["tests/Fake/FakeAnnotateClassNoName.php"] + }, "suggest": { "ray/di": "Guice style annotation-driven dependency injection framework" }, diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 7c4b63d9..00000000 --- a/composer.lock +++ /dev/null @@ -1,184 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "d77523f903fa09fb49fb9be60f1ef959", - "packages": [ - { - "name": "doctrine/annotations", - "version": "v1.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "6a6bec0670bb6e71a263b08bc1b98ea242928633" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/6a6bec0670bb6e71a263b08bc1b98ea242928633", - "reference": "6a6bec0670bb6e71a263b08bc1b98ea242928633", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "php": ">=5.3.2" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Annotations\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2014-09-25 16:45:30" - }, - { - "name": "doctrine/lexer", - "version": "v1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "v1.0" - }, - "dist": { - "type": "zip", - "url": "https://github.com/doctrine/lexer/archive/v1.0.zip", - "reference": "v1.0", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com", - "homepage": "http://www.instaclick.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", - "role": "Developer of wrapped JMSSerializerBundle" - } - ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "lexer", - "parser" - ], - "time": "2013-01-12 18:59:04" - }, - { - "name": "nikic/php-parser", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "b9a60372f26356feb85b4b9ca50a395a5f0d7f34" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/b9a60372f26356feb85b4b9ca50a395a5f0d7f34", - "reference": "b9a60372f26356feb85b4b9ca50a395a5f0d7f34", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "lib/bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "time": "2014-10-14 19:40:07" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "platform": { - "php": ">=5.4.0" - }, - "platform-dev": [] -} diff --git a/src/AnnotatedMatcher.php b/src/AnnotatedMatcher.php new file mode 100644 index 00000000..469f3530 --- /dev/null +++ b/src/AnnotatedMatcher.php @@ -0,0 +1,18 @@ +annotation = $arguments[0]; + } +} diff --git a/src/Bind.php b/src/Bind.php index d3f5f373..4f093861 100644 --- a/src/Bind.php +++ b/src/Bind.php @@ -6,8 +6,10 @@ */ namespace Ray\Aop; +use Doctrine\Common\Annotations\AnnotationReader; use ReflectionClass; use ReflectionMethod; +use Doctrine\Common\Annotations\Reader; final class Bind implements BindInterface { @@ -17,58 +19,80 @@ final class Bind implements BindInterface private $bindings = []; /** - * {@inheritdoc} + * @var AnnotationReader + */ + private $reader; + + public function __construct() + { + $this->reader = new AnnotationReader; + } + + /** + * @param string $class + * @param array $pointcuts + * + * @return $this */ public function bind($class, array $pointcuts) { - foreach ($pointcuts as $pointcut) { - /** @var $pointcut Pointcut */ - $this->bindPointcut(new \ReflectionClass($class), $pointcut); - } + $pointcuts = $this->getAnnnotationPointcuts($pointcuts); + $this->annotatedMethodsMatch(new \ReflectionClass($class), $pointcuts); return $this; } /** * @param ReflectionClass $class - * @param Pointcut $pointcut + * @param array $pointcuts */ - private function bindPointcut(\ReflectionClass $class, Pointcut $pointcut) + private function annotatedMethodsMatch(\ReflectionClass $class, array &$pointcuts) { - $isClassMatch = $pointcut->classMatcher->matchesClass($class, $pointcut->classMatcher->getArguments()); - if ($isClassMatch === false) { - - return; + $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC); + foreach ($methods as $method) { + $this->annotatedMethodMatch($class, $method, $pointcuts); } - $this->methodsMatch($class, $pointcut->methodMatcher, $pointcut->interceptors); } /** - * @param ReflectionClass $class - * @param AbstractMatcher $methodMatcher - * @param Interceptor[] $interceptors + * @param ReflectionClass $class + * @param ReflectionMethod $method + * @param array $pointcuts */ - private function methodsMatch(\ReflectionClass $class, AbstractMatcher $methodMatcher, array $interceptors) + private function annotatedMethodMatch(\ReflectionClass $class, \ReflectionMethod $method, array &$pointcuts) { - $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC); - foreach ($methods as $method) { - $this->methodMatch($method, $methodMatcher, $interceptors); + $annotations = $this->reader->getMethodAnnotations($method); + foreach ($annotations as $annotation) { + $annotationIndex = get_class($annotation); + if (isset($pointcuts[$annotationIndex])) { + $this->annotatedMethodMatchBind($class, $method, $pointcuts[$annotationIndex]); + unset($pointcuts[$annotationIndex]); + } + } + foreach ($pointcuts as $pointcut) { + $this->annotatedMethodMatchBind($class, $method, $pointcut); } } /** + * @param ReflectionClass $class * @param ReflectionMethod $method - * @param AbstractMatcher $methodMatcher - * @param Interceptor[] $interceptors + * @param PointCut $pointCut */ - private function methodMatch(\ReflectionMethod $method, AbstractMatcher $methodMatcher, array $interceptors) + private function annotatedMethodMatchBind(\ReflectionClass $class, \ReflectionMethod $method, PointCut $pointCut) { - $isMethodMatch = $methodMatcher->matchesMethod($method, $methodMatcher->getArguments()); - if ($isMethodMatch) { - $this->bindInterceptors($method->name, $interceptors); + $isMethodMatch = $pointCut->methodMatcher->matchesMethod($method, $pointCut->methodMatcher->getArguments()); + if (! $isMethodMatch) { + return; } + $isClassMatch = $pointCut->classMatcher->matchesClass($class, $pointCut->classMatcher->getArguments()); + if (! $isClassMatch) { + return; + } + $this->bindInterceptors($method->name, $pointCut->interceptors); } + /** * {@inheritdoc} */ @@ -91,7 +115,7 @@ public function getBindings() } /** - * {@inheritdoc} + * @return string */ public function __toString() { @@ -101,4 +125,20 @@ public function __toString() return $shortHash(serialize($this->bindings)); } + + /** + * @param Pointcut[] $pointcuts + */ + public function getAnnnotationPointcuts(array &$pointcuts) + { + $keyPointcuts = []; + foreach ($pointcuts as $key => $pointcut) { + if ($pointcut->methodMatcher instanceof AnnotatedMatcher) { + $key = $pointcut->methodMatcher->annotation; + } + $keyPointcuts[$key] = $pointcut; + } + + return $keyPointcuts; + } } diff --git a/src/BindInterface.php b/src/BindInterface.php index 63461193..82cc51d7 100644 --- a/src/BindInterface.php +++ b/src/BindInterface.php @@ -6,7 +6,7 @@ */ namespace Ray\Aop; -interface BIndInterface +interface BindInterface { /** * Bind pointcuts diff --git a/src/BuiltinMatcher.php b/src/BuiltinMatcher.php index be4debf4..c664e3bb 100644 --- a/src/BuiltinMatcher.php +++ b/src/BuiltinMatcher.php @@ -6,9 +6,8 @@ */ namespace Ray\Aop; -final class BuiltinMatcher extends AbstractMatcher +class BuiltinMatcher extends AbstractMatcher { - /** * @var array */ @@ -34,7 +33,6 @@ public function __construct($matcherName, array $arguments) $this->arguments = $arguments; $matcher = 'Ray\Aop\Matcher\\' . ucwords($this->matcherName) . 'Matcher'; $this->matcher = (new \ReflectionClass($matcher))->newInstance(); - } /** diff --git a/src/CodeGen.php b/src/CodeGen.php index d3b4ce82..d8126a80 100644 --- a/src/CodeGen.php +++ b/src/CodeGen.php @@ -8,8 +8,10 @@ use PhpParser\BuilderFactory; use PhpParser\Node\Stmt\Class_; +use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpParser\PrettyPrinter\Standard; +use PhpParser\Node\Stmt; final class CodeGen implements CodeGenInterface { @@ -63,8 +65,29 @@ public function generate($class, \ReflectionClass $sourceClass) ->getNode(); $stmt = $this->addClassDocComment($stmt, $sourceClass); $code = $this->printer->prettyPrint([$stmt]); + $statements = $this->getUseStatements($sourceClass); - return $code; + return $statements . $code; + } + + /** + * @param \ReflectionClass $class + * + * @return string + */ + private function getUseStatements(\ReflectionClass $class) + { + $traverser = new NodeTraverser; + $useStmtsVisitor = new CodeGenVisitor; + $traverser->addVisitor($useStmtsVisitor); + // parse + $stmts = $this->parser->parse(file_get_contents($class->getFileName())); + // traverse + $traverser->traverse($stmts); + // pretty print + $code = $this->printer->prettyPrint($useStmtsVisitor()); + + return (string) $code; } /** diff --git a/src/CodeGenVisitor.php b/src/CodeGenVisitor.php new file mode 100644 index 00000000..10673b13 --- /dev/null +++ b/src/CodeGenVisitor.php @@ -0,0 +1,34 @@ +use[] = $node; + } + } + + /** + * @return Node\Stmt\Use_[] + */ + public function __invoke() + { + return $this->use; + } +} diff --git a/src/Joinpoint.php b/src/Joinpoint.php index 149b2e5c..01b6d6fa 100644 --- a/src/Joinpoint.php +++ b/src/Joinpoint.php @@ -46,12 +46,4 @@ public function proceed(); * @return object (can be null if the accessible object is static). */ public function getThis(); - - /* - * Returns the static part of this joinpoint. - * - *

The static part is an accessible object on which a chain of - * interceptors are installed. - */ - // public function getStaticPart(); } diff --git a/src/Matcher.php b/src/Matcher.php index d39efe2e..0fba0515 100644 --- a/src/Matcher.php +++ b/src/Matcher.php @@ -28,7 +28,7 @@ public function annotatedWith($annotationName) throw new InvalidAnnotation($annotationName); } - return new BuiltinMatcher(__FUNCTION__, [$annotationName]); + return new AnnotatedMatcher(__FUNCTION__, [$annotationName]); } /** diff --git a/src/Matcher/LogicalAndMatcher.php b/src/Matcher/LogicalAndMatcher.php index 1f20a6b5..d60ed60c 100644 --- a/src/Matcher/LogicalAndMatcher.php +++ b/src/Matcher/LogicalAndMatcher.php @@ -1,6 +1,6 @@ method->invokeArgs($this->object, $this->arguments->getArrayCopy()); } $interceptor = array_shift($this->interceptors); + /** @var $interceptor MethodInterceptor */ return $interceptor->invoke($this); } diff --git a/tests/BindTest.php b/tests/BindTest.php index b1a50e02..7f0bd86c 100644 --- a/tests/BindTest.php +++ b/tests/BindTest.php @@ -4,7 +4,6 @@ class BindTest extends \PHPUnit_Framework_TestCase { - /** * @var Bind */ @@ -74,5 +73,20 @@ public function testNotClassMatch() $this->assertArrayNotHasKey('getDouble', $this->bind->getBindings()); } + public function testOnionAnnotation() + { + $onion1 = new FakeOnionInterceptor1; + $onion2 = new FakeOnionInterceptor2; + $onion3 = new FakeOnionInterceptor3; + $pointcut0 = new Pointcut((new Matcher)->any(), (new Matcher)->startsWith('XXX'), [$onion1]); + $pointcut1 = new Pointcut((new Matcher)->any(), (new Matcher)->annotatedWith(FakeMarker::class), [$onion1]); + $pointcut2 = new Pointcut((new Matcher)->any(), (new Matcher)->annotatedWith(FakeMarker2::class), [$onion2]); + $pointcut3 = new Pointcut((new Matcher)->any(), (new Matcher)->annotatedWith(FakeMarker3::class), [$onion3]); + $this->bind->bind(FakeAnnotateClass::class, [$pointcut0, $pointcut1, $pointcut2, $pointcut3]); + $actual = $this->bind->getBindings(); + $expect = [ + 'getDouble' => [$onion3, $onion2, $onion1] + ]; + $this->assertSame($expect, $actual); + } } - diff --git a/tests/BuiltInMatcherTest.php b/tests/BuiltInMatcherTest.php index 5e26774c..75cbd033 100644 --- a/tests/BuiltInMatcherTest.php +++ b/tests/BuiltInMatcherTest.php @@ -2,7 +2,7 @@ namespace Ray\Aop; -class BuiltinMatcherTest extends \PHPUnit_Framework_TestCase +class BuiltInMatcherTest extends \PHPUnit_Framework_TestCase { /** * @var BuiltinMatcher diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index bc4bfda0..1d7c0d2a 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -3,9 +3,6 @@ namespace Ray\Aop; use Doctrine\Common\Annotations\AnnotationReader; -use PhpParser\Lexer; -use Ray\Aop\Interceptor\AbortProceedInterceptor; -use Ray\Aop\Interceptor\DoubleInterceptor; class CompilerTest extends \PHPUnit_Framework_TestCase { @@ -177,4 +174,18 @@ public function testCompileNoBInd() $class = $this->compiler->compile(FakeMock::class, new Bind); $this->assertSame(FakeMock::class, $class); } + + public function testAnnotation() + { + $class = $this->compiler->compile(FakeAnnotateClass::class, $this->bind); + $annotations = (new AnnotationReader)->getMethodAnnotations(new \ReflectionMethod($class, 'getDouble')); + $this->assertSame(3, count($annotations)); + } + + public function testNoNamespace() + { + $class = $this->compiler->compile(\FakeAnnotateClassNoName::class, $this->bind); + $annotations = (new AnnotationReader)->getMethodAnnotations(new \ReflectionMethod($class, 'getDouble')); + $this->assertSame(3, count($annotations)); + } } diff --git a/tests/Fake/FakeAnnotateClass.php b/tests/Fake/FakeAnnotateClass.php index 2468f9cb..6da2aa20 100644 --- a/tests/Fake/FakeAnnotateClass.php +++ b/tests/Fake/FakeAnnotateClass.php @@ -2,6 +2,9 @@ namespace Ray\Aop; +use Ray\Aop\FakeMarker; +use Ray\Aop\FakeMarker2; + /** * @Ray\Aop\FakeResource */ @@ -10,7 +13,9 @@ class FakeAnnotateClass public $a = 0; /** - * @Ray\Aop\FakeMarker + * @Ray\Aop\FakeMarker3 + * @FakeMarker2 + * @FakeMarker */ public function getDouble($a) { diff --git a/tests/Fake/FakeAnnotateClassNoName.php b/tests/Fake/FakeAnnotateClassNoName.php new file mode 100644 index 00000000..841d77fa --- /dev/null +++ b/tests/Fake/FakeAnnotateClassNoName.php @@ -0,0 +1,22 @@ +proceed(); + } +} diff --git a/tests/Fake/FakeOnionInterceptor2.php b/tests/Fake/FakeOnionInterceptor2.php new file mode 100644 index 00000000..cac20213 --- /dev/null +++ b/tests/Fake/FakeOnionInterceptor2.php @@ -0,0 +1,11 @@ +proceed(); + } +} diff --git a/tests/Fake/FakeOnionInterceptor3.php b/tests/Fake/FakeOnionInterceptor3.php new file mode 100644 index 00000000..15daf9be --- /dev/null +++ b/tests/Fake/FakeOnionInterceptor3.php @@ -0,0 +1,11 @@ +proceed(); + } +} diff --git a/tests/Fake/FakeResource.php b/tests/Fake/FakeResource.php index 50382c72..ff8645a7 100644 --- a/tests/Fake/FakeResource.php +++ b/tests/Fake/FakeResource.php @@ -1,9 +1,5 @@ addPsr4('Ray\Aop\\', [__DIR__, __DIR__ . '/Fake']); -$loader->add('', 'template'); \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader([$loader, 'loadClass']); + +// cleanup $clear = function ($dir) { $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir),