diff --git a/.travis.yml b/.travis.yml index c34b7f15..754c0310 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,6 @@ before_script: script: - ./vendor/bin/phpunit --coverage-clover=coverage.clover; - if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "${TRAVIS_COMMIT_RANGE}") & if ! echo "${CHANGED_FILES}" | grep -qE "^(\\.php_cs(\\.dist)?|composer\\.lock)$"; then IFS=$'\n' EXTRA_ARGS=('--path-mode=intersection' '--' ${CHANGED_FILES[@]}); fi & php php-cs-fixer-v2.phar fix --config=.php_cs -v --dry-run --stop-on-violation --using-cache=no "${EXTRA_ARGS[@]}"; fi - - if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then ./vendor/bin/phpstan analyse -l max src --no-progress --no-interaction; fi + - if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then ./vendor/bin/phpstan analyse -l max -c phpstan.neon src --no-progress --no-interaction; fi after_script: - if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi diff --git a/phpstan.neon b/phpstan.neon index a6d9041e..f0eb3c14 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,3 +1,5 @@ parameters: excludes_analyse: - %currentWorkingDirectory%/tests/tmp/* + ignoreErrors: + - '#Access to an undefined property PhpParser\\Node\\Expr::\$args#' \ No newline at end of file diff --git a/src/AopTemplateConverter.php b/src/AopTemplateConverter.php new file mode 100644 index 00000000..2c33cba6 --- /dev/null +++ b/src/AopTemplateConverter.php @@ -0,0 +1,65 @@ +method = $method->name; + $proceedArg = []; + foreach ($method->getParameters() as $parameter) { + $this->args[] = new Arg(new Variable($parameter->name)); + $proceedArg[] = new ArrayItem(new Variable($parameter->name)); + } + $this->proceedArg = new Arg(new Node\Expr\Array_($proceedArg)); + } + + public function enterNode(Node $node) + { + if ($node instanceof StaticCall && $node->name === 'templateMethod') { + $node->name = $this->method; + $node->args = $this->args; + + return $node; + } + + return $this->updateReflectiveMethodInvocation2ndParam($node); + } + + private function updateReflectiveMethodInvocation2ndParam(Node $node) : Node + { + if ($node instanceof MethodCall && $node->name === 'proceed') { + $node->var->args[2] = $this->proceedArg; + + return $node; + } + + return $node; + } +} diff --git a/src/Arguments.php b/src/Arguments.php deleted file mode 100644 index 2fdccfd8..00000000 --- a/src/Arguments.php +++ /dev/null @@ -1,13 +0,0 @@ -setReturnType($returnType, $methodStmt); } - $methodInsideStatements = $this->getMethodInsideStatement(); + $methodInsideStatements = $this->getMethodInsideStatement($method); $methodStmt->addStmts($methodInsideStatements); return $this->addMethodDocComment($methodStmt, $method); @@ -135,14 +136,19 @@ private function addMethodDocComment(Method $methodStmt, \ReflectionMethod $meth /** * @return \PhpParser\Node[] */ - private function getMethodInsideStatement() : array + private function getMethodInsideStatement(\ReflectionMethod $method) : array { + $traverser = new NodeTraverser; + $traverser->addVisitor(new AopTemplateConverter($method)); + $code = file_get_contents(dirname(__DIR__) . '/template/AopTemplate.php'); $node = $this->parser->parse($code)[0]; /* @var $node \PhpParser\Node\Stmt\Class_ */ $node = $node->getMethods()[0]; + // traverse + $stmts = $traverser->traverse($node->stmts); - return $node->stmts; + return $stmts; } /** diff --git a/src/ReflectiveMethodInvocation.php b/src/ReflectiveMethodInvocation.php index d2d7be43..8c092bc8 100644 --- a/src/ReflectiveMethodInvocation.php +++ b/src/ReflectiveMethodInvocation.php @@ -16,12 +16,12 @@ final class ReflectiveMethodInvocation implements MethodInvocation private $object; /** - * @var Arguments + * @var array|\ArrayObject */ private $arguments; /** - * @var \ReflectionMethod + * @var string */ private $method; @@ -32,14 +32,14 @@ final class ReflectiveMethodInvocation implements MethodInvocation /** * @param object $object - * @param \ReflectionMethod $method - * @param Arguments $arguments + * @param string $method + * @param array $arguments * @param MethodInterceptor[] $interceptors */ public function __construct( $object, - \ReflectionMethod $method, - Arguments $arguments, + string $method, + array $arguments, array $interceptors = [] ) { $this->object = $object; @@ -55,13 +55,13 @@ public function getMethod() : \ReflectionMethod { if ($this->object instanceof WeavedInterface) { $class = (new \ReflectionObject($this->object))->getParentClass(); - $method = new ReflectionMethod($class->name, $this->method->name); - $method->setObject($this->object, $this->method); + $method = new ReflectionMethod($class->name, $this->method); + $method->setObject($this->object, $method); return $method; } - return $this->method; + return new ReflectionMethod($this->object, $this->method); } /** @@ -69,6 +69,8 @@ public function getMethod() : \ReflectionMethod */ public function getArguments() : \ArrayObject { + $this->arguments = new \ArrayObject($this->arguments); + return $this->arguments; } @@ -93,10 +95,9 @@ public function getNamedArguments() : \ArrayObject public function proceed() { if ($this->interceptors === []) { - return $this->method->invokeArgs($this->object, $this->arguments->getArrayCopy()); + return call_user_func_array([$this->object, $this->method], (array) $this->arguments); } $interceptor = array_shift($this->interceptors); - /* @var $interceptor MethodInterceptor */ return $interceptor->invoke($this); } diff --git a/template/AopTemplate.php b/template/AopTemplate.php index 241ef10b..6eba9595 100644 --- a/template/AopTemplate.php +++ b/template/AopTemplate.php @@ -9,6 +9,7 @@ * @see http://stackoverflow.com/questions/1796100/what-is-faster-many-ifs-or-else-if * @see http://stackoverflow.com/questions/2401478/why-is-faster-than-in-php */ + class AopTemplate extends \Ray\Aop\FakeMock implements Ray\Aop\WeavedInterface { /** @@ -17,6 +18,7 @@ class AopTemplate extends \Ray\Aop\FakeMock implements Ray\Aop\WeavedInterface * [$methodName => [$interceptorA[]][] */ public $bindings; + /** * @var bool */ @@ -27,27 +29,19 @@ class AopTemplate extends \Ray\Aop\FakeMock implements Ray\Aop\WeavedInterface * * @param mixed $a */ - public function returnSame($a) + public function templateMethod($a, $b) { - if (isset($this->bindings[__FUNCTION__]) === false) { - return call_user_func_array('parent::' . __FUNCTION__, func_get_args()); - } - if ($this->isIntercepting === false) { $this->isIntercepting = true; - return call_user_func_array('parent::' . __FUNCTION__, func_get_args()); + return parent::templateMethod($a, $b); } $this->isIntercepting = false; - $invocationResult = (new \Ray\Aop\ReflectiveMethodInvocation( - $this, - new \ReflectionMethod($this, __FUNCTION__), - new \Ray\Aop\Arguments(func_get_args()), - $this->bindings[__FUNCTION__] - ))->proceed(); + // invoke interceptor + $result = (new \Ray\Aop\ReflectiveMethodInvocation($this, __FUNCTION__, [$a, $b], $this->bindings[__FUNCTION__]))->proceed(); $this->isIntercepting = true; - return $invocationResult; + return $result; } } diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index 01cfe705..81981a5e 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -261,4 +261,14 @@ public function testMethodAnnotationReaderReturnNull() $this->assertNull(FakeMethodAnnotationReaderInterceptor::$methodAnnotation); $this->assertCount(0, FakeMethodAnnotationReaderInterceptor::$methodAnnotations); } + + public function testInterceptorCanChangeArgument() + { + $bind = (new Bind)->bindInterceptors('returnSame', [new FakeChangeArgsInterceptor()]); + $compiler = new Compiler($_ENV['TMP_DIR']); + /** @var FakeMock $mock */ + $mock = $compiler->newInstance(FakeMock::class, [], $bind); + $mock->returnSame(1); + $this->assertSame('changed', $mock->returnSame(1)); + } } diff --git a/tests/Fake/FakeChangeArgsInterceptor.php b/tests/Fake/FakeChangeArgsInterceptor.php new file mode 100644 index 00000000..eae84763 --- /dev/null +++ b/tests/Fake/FakeChangeArgsInterceptor.php @@ -0,0 +1,13 @@ +getArguments(); + $args[0] = 'changed'; + + return $invocation->proceed(); + } +} diff --git a/tests/Fake/FakeWeaved.php b/tests/Fake/FakeWeaved.php index a83fc6a4..6b557c6e 100644 --- a/tests/Fake/FakeWeaved.php +++ b/tests/Fake/FakeWeaved.php @@ -27,7 +27,7 @@ public function returnSame($a) $invocation = new ReflectiveMethodInvocation( $this, new \ReflectionMethod($this, __FUNCTION__), - new Arguments(func_get_args()), + func_get_args(), $interceptors ); diff --git a/tests/ReflectiveMethodInvocationTest.php b/tests/ReflectiveMethodInvocationTest.php index ef03fdfc..5415b929 100644 --- a/tests/ReflectiveMethodInvocationTest.php +++ b/tests/ReflectiveMethodInvocationTest.php @@ -26,7 +26,7 @@ protected function setUp() { parent::setUp(); $this->fake = new FakeClass; - $this->invocation = new ReflectiveMethodInvocation($this->fake, new \ReflectionMethod($this->fake, 'add'), new Arguments([1])); + $this->invocation = new ReflectiveMethodInvocation($this->fake, 'add', [1]); } public function testGetMethod() @@ -70,7 +70,7 @@ public function testGetThis() public function testGetParentMethod() { $fake = new FakeWeavedClass; - $invocation = new ReflectiveMethodInvocation($fake, new \ReflectionMethod($fake, 'add'), new Arguments([1])); + $invocation = new ReflectiveMethodInvocation($fake, 'add', [1]); $method = $invocation->getMethod(); $this->assertSame(FakeClass::class, $method->class); $this->assertSame('add', $method->name); @@ -79,7 +79,7 @@ public function testGetParentMethod() public function testProceedMultipleInterceptors() { $fake = new FakeWeavedClass; - $invocation = new ReflectiveMethodInvocation($fake, new \ReflectionMethod($fake, 'add'), new Arguments([1]), [new FakeInterceptor, new FakeInterceptor]); + $invocation = new ReflectiveMethodInvocation($fake, 'add', [1], [new FakeInterceptor, new FakeInterceptor]); $invocation->proceed(); $this->assertSame(1, $fake->a); }