From f05982a74459d5b0a245b62d6267cb4c122332b8 Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 18 Oct 2017 20:25:44 +0200 Subject: [PATCH] Sometimes I want to quickly develop something without the need of creating a visitor class. I created this class and I think more people want to use this. --- lib/PhpParser/NodeVisitor/CallableVisitor.php | 95 +++++++++++ .../NodeVisitor/CallableVisitorTest.php | 158 ++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 lib/PhpParser/NodeVisitor/CallableVisitor.php create mode 100644 test/PhpParser/NodeVisitor/CallableVisitorTest.php diff --git a/lib/PhpParser/NodeVisitor/CallableVisitor.php b/lib/PhpParser/NodeVisitor/CallableVisitor.php new file mode 100644 index 0000000000..2722e95a33 --- /dev/null +++ b/lib/PhpParser/NodeVisitor/CallableVisitor.php @@ -0,0 +1,95 @@ + null, + 'enterNode' => null, + 'leaveNode' => null, + 'afterTraverse' => null, + ]; + + /** + * @param array $nodes + */ + public function beforeTraverse(array $nodes) + { + return $this->callCallable(__FUNCTION__, $nodes); + } + + /** + * @param Node $node + */ + public function enterNode(Node $node) + { + return $this->callCallable(__FUNCTION__, $node); + } + + /** + * @param Node $node + */ + public function leaveNode(Node $node) + { + return $this->callCallable(__FUNCTION__, $node); + } + + /** + * @param array $nodes + */ + public function afterTraverse(array $nodes) + { + return $this->callCallable(__FUNCTION__, $nodes); + } + + /** + * Call a closure for given callable name. + * + * @param $callableName + * @param $argument + */ + private function callCallable($callableName, $argument) + { + if (!empty($this->callables[$callableName])) { + return $this->callables[$callableName]($argument); + } + } + + /** + * Sets the callable for one of the methods the traverser calls. + * + * @param $method + * @param $arguments + * @return bool + */ + public function __call($method, $arguments) + { + $name = lcfirst(str_replace('set', '', $method)); + + if (!isset($arguments[0]) || !is_callable($arguments[0])) { + return false; + } + + if (array_key_exists($name, $this->callables)) { + $this->callables[$name] = $arguments[0]; + } + + return true; + } +} \ No newline at end of file diff --git a/test/PhpParser/NodeVisitor/CallableVisitorTest.php b/test/PhpParser/NodeVisitor/CallableVisitorTest.php new file mode 100644 index 0000000000..eae9eb02e8 --- /dev/null +++ b/test/PhpParser/NodeVisitor/CallableVisitorTest.php @@ -0,0 +1,158 @@ +enterNode = 0; + $this->leaveNode = 0; + $this->beforeTraverse = 0; + $this->afterTraverse = 0; + + $this->order = []; + } + + /** + * Test if the CallableVisitor works as expected with all callables set. + */ + public function testWithClosures() + { + $this->callableVisitorTest(function (Node $node) { + $this->order[] = 'enterNode'; + $this->enterNode++; + }, function (Node $node) { + $this->order[] = 'leaveNode'; + $this->leaveNode++; + }, function ($array) { + $this->order[] = 'beforeTraverse'; + $this->beforeTraverse++; + }, function ($array) { + $this->order[] = 'afterTraverse'; + $this->afterTraverse++; + }); + + $this->assertSame([ + $this->enterNode, + $this->leaveNode, + $this->beforeTraverse, + $this->afterTraverse + ], [6, 6, 1, 1]); + + $this->assertSame([ + 'beforeTraverse', + 'enterNode', + 'enterNode', + 'enterNode', + 'leaveNode', + 'enterNode', + 'enterNode', + 'leaveNode', + 'enterNode', + 'leaveNode', + 'leaveNode', + 'leaveNode', + 'leaveNode', + 'afterTraverse', + ], $this->order); + } + + /** + * Test if the CallableVisitor works as expected with all enter node/traverse callables set. + */ + public function testPartiallyFilled() + { + $this->callableVisitorTest(function (Node $node) { + $this->order[] = 'enterNode'; + $this->enterNode++; + }, null, function ($array) { + $this->order[] = 'beforeTraverse'; + $this->beforeTraverse++; + }, null); + + $this->assertSame([ + $this->enterNode, + $this->leaveNode, + $this->beforeTraverse, + $this->afterTraverse + ], [6, 0, 1, 0]); + + $this->assertSame([ + 'beforeTraverse', + 'enterNode', + 'enterNode', + 'enterNode', + 'enterNode', + 'enterNode', + 'enterNode', + ], $this->order); + } + + /** + * @param $enterNode + * @param $leaveNode + * @param $beforeTravers + * @param $afterTravers + */ + private function callableVisitorTest($enterNode, $leaveNode, $beforeTravers, $afterTravers) + { + $traverser = new NodeTraverser(); + $visitor = new CallableVisitor(); + + $visitor->setEnterNode($enterNode); + if (is_callable($enterNode)) { + $visitor->setLeaveNode($leaveNode); + } + + $visitor->setBeforeTraverse($beforeTravers); + if (is_callable($afterTravers)) { + $visitor->setAfterTraverse($afterTravers); + } + + $traverser->addVisitor($visitor); + + $assign = new Expr\Assign(new Expr\Variable('a'), new Expr\BinaryOp\Concat( + new Expr\Variable('b'), new Expr\Variable('c') + )); + $stmts = [new Node\Stmt\Expression($assign)]; + + $traverser->traverse($stmts); + } +} \ No newline at end of file