Don't use references in NodeTraverser

Stop using reference magic in the NodeTraverser. Instead use normal return values. Additionally enfore that an array is passed to ->traverse().
This commit is contained in:
nikic 2011-09-24 23:37:47 +02:00
parent d5f148b43f
commit d53173c2f3
5 changed files with 107 additions and 75 deletions

View File

@ -139,29 +139,29 @@ NodeTraverser
-------------
The node traverser allows traversing the node tree using a visitor class. A visitor class must
implement the `NodeVisitorInterface`, which defines the following four methods:
implement the `NodeVisitor` interface, which defines the following four methods:
public function beforeTraverse(&$node);
public function enterNode(PHPParser_Node &$node);
public function leaveNode(PHPParser_Node &$node);
public function afterTraverse(&$node);
public function beforeTraverse(array $nodes);
public function enterNode(PHPParser_Node $node);
public function leaveNode(PHPParser_Node $node);
public function afterTraverse(array $nodes);
The `beforeTraverse` method is called once before the traversal begins and is passed the node the
The `beforeTraverse` method is called once before the traversal begins and is passed the nodes the
traverser was called with. This method can be used for resetting values before traversation or
preparing the tree for traversal.
The `afterTraverse` method is similar to the `beforeTraverse` method, with the only difference that
it is called once after the traversal.
The `enterNode` and `leaveNode` methods are called on every node, the former when it is entered, i.e.
before its subnodes are traversed, the latter when it is left.
The `enterNode` and `leaveNode` methods are called on every node, the former when it is entered,
i.e. before its subnodes are traversed, the latter when it is left.
The node is passed into all four functions by reference, i.e. the node may be transformed or even
replaced in any way. (As the node is passed by reference it obviously shouldn't be returned after
modifiation.) Additionally `leaveNode` can return two special values: If `false` is returned the
current node will be completely deleted. If an `array` is returned the current node will be replaced
with with an array of other nodes. I.e. if in `array(A, B, C)` the node `B` should be replaced with
`array(X, Y, Z)` the result will be `array(A, X, Y, Z, C)`.
All four methods can either return the changed node or not return at all (or return `null`) in which
case the current node is not changed. The `leaveNode` method can furthermore return two special
values: If `false` is returned the current node will be removed from the parent array. If an `array`
is returned the current node will be merged into the parent array at the offset of the current node.
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will be
`array(A, X, Y, Z, C)`.
The above described visitors are registered in the `NodeTraverser` class:
@ -171,15 +171,13 @@ The above described visitors are registered in the `NodeTraverser` class:
$traverser->addVisitor($visitor);
$stmts = $parser->parse($lexer);
// ->traverse() directly modifies $stmts. Do *not* write $stmts = $traverser->traverse($stmts);
$traverser->traverse($stmts);
$stmts = $traverser->traverse($stmts);
With `MyVisitor` being something like that:
class MyVisitor extends PHPParser_NodeVisitorAbstract
{
public function enterNode(PHPParser_Node &$node) {
public function enterNode(PHPParser_Node $node) {
// ...
}
}

View File

@ -3,7 +3,7 @@
class PHPParser_NodeTraverser
{
/**
* @var PHPParser_NodeVisitorInterface[] Visitors
* @var PHPParser_NodeVisitor[] Visitors
*/
protected $visitors;
@ -17,61 +17,69 @@ class PHPParser_NodeTraverser
/**
* Adds a visitor.
*
* @param PHPParser_NodeVisitorInterface $visitor Visitor to add
* @param PHPParser_NodeVisitor $visitor Visitor to add
*/
public function addVisitor(PHPParser_NodeVisitorInterface $visitor) {
public function addVisitor(PHPParser_NodeVisitor $visitor) {
$this->visitors[] = $visitor;
}
/**
* Traverses a node or an array using the registered visitors.
* Traverses an array of nodes using the registered visitors.
*
* @param array|PHPParser_Node $node Node or array
* @param PHPParser_Node[] $nodes Array of nodes
*
* @return PHPParser_Node[] Traversed array of nodes
*/
public function traverse(&$node) {
if (!is_array($node) && !$node instanceof PHPParser_Node) {
throw new InvalidArgumentException('Can only traverse nodes and arrays');
public function traverse(array $nodes) {
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->beforeTraverse($nodes)) {
$nodes = $return;
}
}
$nodes = $this->_traverse($nodes);
foreach ($this->visitors as $visitor) {
$visitor->beforeTraverse($node);
if (null !== $return = $visitor->afterTraverse($nodes)) {
$nodes = $return;
}
}
$this->_traverse($node, $this->visitors);
foreach ($this->visitors as $visitor) {
$visitor->afterTraverse($node);
}
return $nodes;
}
protected function _traverse(&$node, array $visitors) {
protected function _traverse($node) {
$doNodes = array();
foreach ($node as $subNodeKey => &$subNode) {
foreach ($node as $name => $subNode) {
if (is_array($subNode)) {
$this->_traverse($subNode, $visitors);
$node[$name] = $this->_traverse($subNode, $this->visitors);
} elseif ($subNode instanceof PHPParser_Node) {
foreach ($visitors as $visitor) {
$visitor->enterNode($subNode);
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->enterNode($subNode)) {
$node[$name] = $return;
}
}
$this->_traverse($subNode, $visitors);
$node[$name] = $this->_traverse($subNode, $this->visitors);
foreach ($visitors as $i => $visitor) {
foreach ($this->visitors as $i => $visitor) {
$return = $visitor->leaveNode($subNode);
if (false === $return) {
$doNodes[] = array($subNodeKey, array());
$doNodes[] = array($name, array());
break;
} elseif (is_array($return)) {
// traverse replacement nodes using all visitors apart from the one that
// did the change
$subNodeVisitors = $visitors;
unset($subNodeVisitors[$i]);
$this->_traverse($return, $subNodeVisitors);
unset($this->visitors[$i]);
$return = $this->_traverse($return);
$this->visitors[$i] = $visitor;
$doNodes[] = array($subNodeKey, $return);
$doNodes[] = array($name, $return);
break;
} elseif (null !== $return) {
$node[$name] = $return;
}
}
}
@ -86,5 +94,7 @@ class PHPParser_NodeTraverser
array_splice($node, $key, 1, $replace);
}
}
return $node;
}
}

View File

@ -1,32 +1,58 @@
<?php
interface PHPParser_NodeVisitorInterface
interface PHPParser_NodeVisitor
{
/**
* Called once before traversal.
*
* @param $node
* Return value semantics:
* * null: $nodes stays as-is
* * otherwise: $nodes is set to the return value
*
* @param PHPParser_Node[] $nodes Array of nodes
*
* @return null|PHPParser_Node[] Array of nodes
*/
public function beforeTraverse($node);
public function beforeTraverse(array $nodes);
/**
* Called when entering a node.
*
* @param PHPParser_Node $node
* Return value semantics:
* * null: $node stays as-is
* * otherwise: $node is set to the return value
*
* @param PHPParser_Node $node Node
*
* @return null|PHPParser_Node Node
*/
public function enterNode(PHPParser_Node $node);
/**
* Called when leaving a node.
*
* @param PHPParser_Node $node
* Return value semantics:
* * null: $node stays as-is
* * false: $node is removed from the parent array
* * array: The return value is merged into the parent array (at the position of the $node)
* * otherwise: $node is set to the return value
*
* @param PHPParser_Node $node Node
*
* @return null|PHPParser_Node|false|PHPParser_Node[] Node
*/
public function leaveNode(PHPParser_Node $node);
/**
* Called once after traversal.
*
* @param $node
* Return value semantics:
* * null: $nodes stays as-is
* * otherwise: $nodes is set to the return value
*
* @param PHPParser_Node[] $nodes Array of nodes
*
* @return null|PHPParser_Node[] Array of nodes
*/
public function afterTraverse($node);
public function afterTraverse(array $nodes);
}

View File

@ -1,9 +1,9 @@
<?php
class PHPParser_NodeVisitorAbstract implements PHPParser_NodeVisitorInterface
class PHPParser_NodeVisitorAbstract implements PHPParser_NodeVisitor
{
public function beforeTraverse(&$node) { }
public function enterNode(PHPParser_Node &$node) { }
public function leaveNode(PHPParser_Node &$node) { }
public function afterTraverse(&$node) { }
public function beforeTraverse(array $nodes) { }
public function enterNode(PHPParser_Node $node) { }
public function leaveNode(PHPParser_Node $node) { }
public function afterTraverse(array $nodes) { }
}

View File

@ -21,13 +21,14 @@ class PHPParser_Tests_NodeTraverserTest extends PHPUnit_Framework_TestCase
public function testTraverse() {
$node = $this->getTestNode();
$visitor = new PHPParser_Tests_NodeVisitor;
$visitor = new PHPParser_Tests_NodeVisitor;
$traverser = new PHPParser_NodeTraverser;
$traverser->addVisitor($visitor);
$traverser->traverse($node);
$this->assertEquals($node, $visitor->beforeTraverseNode);
$node = $traverser->traverse($node);
$this->assertEquals($node, $visitor->beforeTraverseNodes);
$this->assertEquals(
array(
@ -53,17 +54,14 @@ class PHPParser_Tests_NodeTraverserTest extends PHPUnit_Framework_TestCase
$visitor->leftNodes
);
$this->assertEquals($node, $visitor->afterTraverseNode);
$this->assertEquals($node, $visitor->afterTraverseNodes);
}
public function testModifyingTraverse() {
$node = $this->getTestNode();
$visitor = new PHPParser_Tests_ModifyingNodeVisitor;
$traverser = new PHPParser_NodeTraverser;
$traverser->addVisitor($visitor);
$traverser->traverse($node);
$traverser->addVisitor(new PHPParser_Tests_ModifyingNodeVisitor);
$this->assertEquals(
array(
@ -71,42 +69,42 @@ class PHPParser_Tests_NodeTraverserTest extends PHPUnit_Framework_TestCase
new PHPParser_Node_Scalar_String('Foo Bar')
)),
),
$node
$traverser->traverse($node)
);
}
}
class PHPParser_Tests_NodeVisitor extends PHPParser_NodeVisitorAbstract
{
public $beforeTraverseNode;
public $beforeTraverseNodes;
public $enteredNodes;
public $leftNodes;
public $afterTraverseNode;
public $afterTraverseNodes;
public function __construct() {
$this->enteredNodes = $this->leftNodes = array();
}
public function beforeTraverse(&$node) {
$this->beforeTraverseNode = $node;
public function beforeTraverse(array $nodes) {
$this->beforeTraverseNodes = $nodes;
}
public function enterNode(PHPParser_Node &$node) {
public function enterNode(PHPParser_Node $node) {
$this->enteredNodes[] = $node->getType();
}
public function leaveNode(PHPParser_Node &$node) {
public function leaveNode(PHPParser_Node $node) {
$this->leftNodes[] = $node->getType();
}
public function afterTraverse(&$node) {
$this->afterTraverseNode = $node;
public function afterTraverse(array $nodes) {
$this->afterTraverseNodes = $nodes;
}
}
class PHPParser_Tests_ModifyingNodeVisitor extends PHPParser_NodeVisitorAbstract
{
public function leaveNode(PHPParser_Node &$node) {
public function leaveNode(PHPParser_Node $node) {
// delete namespace nodes by merging them
if ($node instanceof PHPParser_Node_Stmt_Namespace) {
return $node->stmts;