1
0
mirror of https://github.com/danog/PHP-Parser.git synced 2024-11-26 20:04:48 +01:00

Add an example

This commit is contained in:
nikic 2011-11-12 18:29:50 +01:00
parent 2ec6ae4b03
commit 3b02facf0c
3 changed files with 141 additions and 7 deletions

View File

@ -226,4 +226,128 @@ The `NameResolver` takes care of that and resolves names as far as possible.
After running it most names will be fully qualified. The only names that will stay unqualified are
unqualified function and constant names. These are resolved at runtime and thus the visitor can't
know which function they are referring to. In most cases this is a non-issue as the global functions
are meant.
are meant.
Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations
that contains the namespaced name instead of only the shortname that is available via `name`.
Example: Converting namespaced code to pseudo namespaces
--------------------------------------------------------
A small example to understand the concept: We want to convert namespaced code to pseudo namespaces
so it works on 5.2, i.e. name like `A\\B` should be converted to `A_B`. Note that such conversions
are fairly complicated if you take PHP's dynamic features into account, so our conversion will
assume that no dynamic features are used.
We start off with the following base code:
const IN_DIR = '/some/path';
const OUT_DIR = '/some/other/path';
$parser = new PHPParser_Parser;
$traverser = new PHPParser_NodeTraverser;
$prettyPrinter = new PHPParser_PrettyPrinter_Zend;
$traverser->addVisitor(new PHPParser_NodeVisitor_NameResolver); // we will need resolved names
$traverser->addVisitor(new NodeVisitor_NamespaceConverter); // our own node visitor
// iterate over all files in the directory
foreach (new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(IN_DIR),
RecursiveIteratorIterator::LEAVES_ONLY)
as $file) {
// only convert .php files
if (!preg_match('~\.php$~', $file)) {
continue;
}
try {
// read the file that should be converted
$code = file_get_contents($file);
// parse
$stmts = $parser->parse(new PHPParser_Lexer($code));
// traverse
$stmts = $traverser->traverse($stmts);
// pretty print
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts);
// write the converted file to the target directory
file_put_contents(
substr_replace($file->getPathname(), OUT_DIR, 0, strlen(IN_DIR)),
$code
);
} catch (PHPParser_Error $e) {
echo 'Parse Error: ', $e->getMessage();
}
}
Now lets start with the main code, the `NodeVisitor_NamespaceConverter`. One thing it needs to do
is convert `A\\B` style names to `A_B` style ones.
class NodeVisitor_NamespaceConverter extends PHPParser_NodeVisitorAbstract
{
public function leaveNode(PHPParser_Node $node) {
if ($node instanceof PHPParser_Node_Name) {
return new PHPParser_Node_Name($node->toString('_'));
}
}
}
The above code profits from the fact that the `NameResolver` already resolved all names as far as
possible, so we don't need to do that. All the need to create a string with the name parts separated
by underscores instead of backslashes. This is what `$node->toString('_')` does. (If you want to
create a name with backslashes either write `$node->toString()` or `(string) $node`.) Then we create
a new name from the string and return it. Returning a new node replaces the old node.
Another thing we need to do is change the class/function/const declarations. Currently they contain
only the shortname (i.e. the last part of the name), but they need to contain the complete class
name:
class NodeVisitor_NamespaceConverter extends PHPParser_NodeVisitorAbstract
{
public function leaveNode(PHPParser_Node $node) {
if ($node instanceof PHPParser_Node_Name) {
return new PHPParser_Node_Name($node->toString('_'));
} elseif ($node instanceof PHPParser_Node_Stmt_Class
|| $node instanceof PHPParser_Node_Stmt_Interface
|| $node instanceof PHPParser_Node_Stmt_Function) {
$node->name = $node->namespacedName->toString('_');
} elseif ($node instanceof PHPParser_Node_Stmt_Const) {
foreach ($node->consts as $const) {
$const->name = $const->namespacedName->toString('_');
}
}
}
}
There is not much more to it than converting the namespaced name to string with `_` as separator.
The last thing we need to do is remove the `namespace` and `use` statements:
class NodeVisitor_NamespaceConverter extends PHPParser_NodeVisitorAbstract
{
public function leaveNode(PHPParser_Node $node) {
if ($node instanceof PHPParser_Node_Name) {
return new PHPParser_Node_Name($node->toString('_'));
} elseif ($node instanceof PHPParser_Node_Stmt_Class
|| $node instanceof PHPParser_Node_Stmt_Interface
|| $node instanceof PHPParser_Node_Stmt_Function) {
$node->name = $node->namespacedName->toString('_');
} elseif ($node instanceof PHPParser_Node_Stmt_Const) {
foreach ($node->consts as $const) {
$const->name = $const->namespacedName->toString('_');
}
} elseif ($node instanceof PHPParser_Node_Stmt_Namespace) {
// returning an array merges is into the parent array
return $node->stmts;
} elseif ($node instanceof PHPParser_Node_Stmt_Use) {
// returning false removed the node altogether
return false;
}
}
}
That's all.

View File

@ -48,6 +48,8 @@ class PHPParser_NodeVisitor_NameResolver extends PHPParser_NodeVisitorAbstract
$interface = $this->resolveClassName($interface);
}
$this->addNamespacedName($node);
} elseif ($node instanceof PHPParser_Node_Stmt_Trait) {
$this->addNamespacedName($node);
} elseif ($node instanceof PHPParser_Node_Stmt_Function) {
$this->addNamespacedName($node);

View File

@ -87,13 +87,17 @@ EOC;
namespace Foo {
class A {}
function B() {}
const C = 'D';
interface B {}
trait C {}
function D() {}
const E = 'F';
}
namespace {
class A {}
function B() {}
const C = 'D';
interface B {}
trait C {}
function D() {}
const E = 'F';
}
EOC;
@ -106,9 +110,13 @@ EOC;
$this->assertEquals('Foo\\A', (string) $stmts[0]->stmts[0]->namespacedName);
$this->assertEquals('Foo\\B', (string) $stmts[0]->stmts[1]->namespacedName);
$this->assertEquals('Foo\\C', (string) $stmts[0]->stmts[2]->consts[0]->namespacedName);
$this->assertEquals('Foo\\C', (string) $stmts[0]->stmts[2]->namespacedName);
$this->assertEquals('Foo\\D', (string) $stmts[0]->stmts[3]->namespacedName);
$this->assertEquals('Foo\\E', (string) $stmts[0]->stmts[4]->consts[0]->namespacedName);
$this->assertEquals('A', (string) $stmts[1]->stmts[0]->namespacedName);
$this->assertEquals('B', (string) $stmts[1]->stmts[1]->namespacedName);
$this->assertEquals('C', (string) $stmts[1]->stmts[2]->consts[0]->namespacedName);
$this->assertEquals('C', (string) $stmts[1]->stmts[2]->namespacedName);
$this->assertEquals('D', (string) $stmts[1]->stmts[3]->namespacedName);
$this->assertEquals('E', (string) $stmts[1]->stmts[4]->consts[0]->namespacedName);
}
}