mirror of
https://github.com/danog/PHP-Parser.git
synced 2024-11-27 04:14:44 +01:00
Add an example
This commit is contained in:
parent
2ec6ae4b03
commit
3b02facf0c
@ -227,3 +227,127 @@ After running it most names will be fully qualified. The only names that will st
|
|||||||
unqualified function and constant names. These are resolved at runtime and thus the visitor can't
|
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
|
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.
|
@ -48,6 +48,8 @@ class PHPParser_NodeVisitor_NameResolver extends PHPParser_NodeVisitorAbstract
|
|||||||
$interface = $this->resolveClassName($interface);
|
$interface = $this->resolveClassName($interface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->addNamespacedName($node);
|
||||||
|
} elseif ($node instanceof PHPParser_Node_Stmt_Trait) {
|
||||||
$this->addNamespacedName($node);
|
$this->addNamespacedName($node);
|
||||||
} elseif ($node instanceof PHPParser_Node_Stmt_Function) {
|
} elseif ($node instanceof PHPParser_Node_Stmt_Function) {
|
||||||
$this->addNamespacedName($node);
|
$this->addNamespacedName($node);
|
||||||
|
@ -87,13 +87,17 @@ EOC;
|
|||||||
|
|
||||||
namespace Foo {
|
namespace Foo {
|
||||||
class A {}
|
class A {}
|
||||||
function B() {}
|
interface B {}
|
||||||
const C = 'D';
|
trait C {}
|
||||||
|
function D() {}
|
||||||
|
const E = 'F';
|
||||||
}
|
}
|
||||||
namespace {
|
namespace {
|
||||||
class A {}
|
class A {}
|
||||||
function B() {}
|
interface B {}
|
||||||
const C = 'D';
|
trait C {}
|
||||||
|
function D() {}
|
||||||
|
const E = 'F';
|
||||||
}
|
}
|
||||||
EOC;
|
EOC;
|
||||||
|
|
||||||
@ -106,9 +110,13 @@ EOC;
|
|||||||
|
|
||||||
$this->assertEquals('Foo\\A', (string) $stmts[0]->stmts[0]->namespacedName);
|
$this->assertEquals('Foo\\A', (string) $stmts[0]->stmts[0]->namespacedName);
|
||||||
$this->assertEquals('Foo\\B', (string) $stmts[0]->stmts[1]->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('A', (string) $stmts[1]->stmts[0]->namespacedName);
|
||||||
$this->assertEquals('B', (string) $stmts[1]->stmts[1]->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);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user