annotate vendor/nikic/php-parser/doc/2_Usage_of_basic_components.markdown @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 Usage of basic components
Chris@0 2 =========================
Chris@0 3
Chris@0 4 This document explains how to use the parser, the pretty printer and the node traverser.
Chris@0 5
Chris@0 6 Bootstrapping
Chris@0 7 -------------
Chris@0 8
Chris@0 9 To bootstrap the library, include the autoloader generated by composer:
Chris@0 10
Chris@0 11 ```php
Chris@0 12 require 'path/to/vendor/autoload.php';
Chris@0 13 ```
Chris@0 14
Chris@0 15 Additionally you may want to set the `xdebug.max_nesting_level` ini option to a higher value:
Chris@0 16
Chris@0 17 ```php
Chris@0 18 ini_set('xdebug.max_nesting_level', 3000);
Chris@0 19 ```
Chris@0 20
Chris@0 21 This ensures that there will be no errors when traversing highly nested node trees. However, it is
Chris@0 22 preferable to disable XDebug completely, as it can easily make this library more than five times
Chris@0 23 slower.
Chris@0 24
Chris@0 25 Parsing
Chris@0 26 -------
Chris@0 27
Chris@0 28 In order to parse code, you first have to create a parser instance:
Chris@0 29
Chris@0 30 ```php
Chris@0 31 use PhpParser\ParserFactory;
Chris@0 32 $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
Chris@0 33 ```
Chris@0 34
Chris@0 35 The factory accepts a kind argument, that determines how different PHP versions are treated:
Chris@0 36
Chris@0 37 Kind | Behavior
Chris@0 38 -----|---------
Chris@0 39 `ParserFactory::PREFER_PHP7` | Try to parse code as PHP 7. If this fails, try to parse it as PHP 5.
Chris@0 40 `ParserFactory::PREFER_PHP5` | Try to parse code as PHP 5. If this fails, try to parse it as PHP 7.
Chris@0 41 `ParserFactory::ONLY_PHP7` | Parse code as PHP 7.
Chris@0 42 `ParserFactory::ONLY_PHP5` | Parse code as PHP 5.
Chris@0 43
Chris@13 44 Unless you have a strong reason to use something else, `PREFER_PHP7` is a reasonable default.
Chris@0 45
Chris@0 46 The `create()` method optionally accepts a `Lexer` instance as the second argument. Some use cases
Chris@0 47 that require customized lexers are discussed in the [lexer documentation](component/Lexer.markdown).
Chris@0 48
Chris@0 49 Subsequently you can pass PHP code (including the opening `<?php` tag) to the `parse` method in order to
Chris@0 50 create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will be thrown:
Chris@0 51
Chris@0 52 ```php
Chris@13 53 <?php
Chris@0 54 use PhpParser\Error;
Chris@0 55 use PhpParser\ParserFactory;
Chris@0 56
Chris@13 57 $code = <<<'CODE'
Chris@13 58 <?php
Chris@13 59 function printLine($msg) {
Chris@13 60 echo $msg, "\n";
Chris@13 61 }
Chris@13 62 printLine('Hello World!!!');
Chris@13 63 CODE;
Chris@13 64
Chris@0 65 $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
Chris@0 66
Chris@0 67 try {
Chris@0 68 $stmts = $parser->parse($code);
Chris@0 69 // $stmts is an array of statement nodes
Chris@0 70 } catch (Error $e) {
Chris@0 71 echo 'Parse Error: ', $e->getMessage();
Chris@0 72 }
Chris@0 73 ```
Chris@0 74
Chris@0 75 A parser instance can be reused to parse multiple files.
Chris@0 76
Chris@13 77 Node dumping
Chris@13 78 ------------
Chris@0 79
Chris@13 80 To dump the abstact syntax tree in human readable form, a `NodeDumper` can be used:
Chris@13 81
Chris@13 82 ```php
Chris@13 83 <?php
Chris@13 84 use PhpParser\NodeDumper;
Chris@13 85
Chris@13 86 $nodeDumper = new NodeDumper;
Chris@13 87 echo $nodeDumper->dump($stmts), "\n";
Chris@13 88 ```
Chris@13 89
Chris@13 90 For the sample code from the previous section, this will produce the following output:
Chris@0 91
Chris@0 92 ```
Chris@0 93 array(
Chris@13 94 0: Stmt_Function(
Chris@13 95 byRef: false
Chris@13 96 name: Identifier(
Chris@13 97 name: printLine
Chris@13 98 )
Chris@13 99 params: array(
Chris@13 100 0: Param(
Chris@13 101 type: null
Chris@13 102 byRef: false
Chris@13 103 variadic: false
Chris@13 104 var: Expr_Variable(
Chris@13 105 name: msg
Chris@13 106 )
Chris@13 107 default: null
Chris@0 108 )
Chris@13 109 )
Chris@13 110 returnType: null
Chris@13 111 stmts: array(
Chris@13 112 0: Stmt_Echo(
Chris@13 113 exprs: array(
Chris@13 114 0: Expr_Variable(
Chris@13 115 name: msg
Chris@13 116 )
Chris@13 117 1: Scalar_String(
Chris@13 118 value:
Chris@13 119
Chris@0 120 )
Chris@0 121 )
Chris@13 122 )
Chris@13 123 )
Chris@13 124 )
Chris@13 125 1: Stmt_Expression(
Chris@13 126 expr: Expr_FuncCall(
Chris@13 127 name: Name(
Chris@13 128 parts: array(
Chris@13 129 0: printLine
Chris@13 130 )
Chris@13 131 )
Chris@13 132 args: array(
Chris@13 133 0: Arg(
Chris@13 134 value: Scalar_String(
Chris@13 135 value: Hello World!!!
Chris@13 136 )
Chris@13 137 byRef: false
Chris@13 138 unpack: false
Chris@0 139 )
Chris@0 140 )
Chris@0 141 )
Chris@0 142 )
Chris@0 143 )
Chris@0 144 ```
Chris@0 145
Chris@13 146 You can also use the `php-parse` script to obtain such a node dump by calling it either with a file
Chris@13 147 name or code string:
Chris@0 148
Chris@13 149 ```sh
Chris@13 150 vendor/bin/php-parse file.php
Chris@13 151 vendor/bin/php-parse "<?php foo();"
Chris@13 152 ```
Chris@13 153
Chris@13 154 This can be very helpful if you want to quickly check how certain syntax is represented in the AST.
Chris@13 155
Chris@13 156 Node tree structure
Chris@13 157 -------------------
Chris@13 158
Chris@13 159 Looking at the node dump above, you can see that `$stmts` for this example code is an array of two
Chris@13 160 nodes, a `Stmt_Function` and a `Stmt_Expression`. The corresponding class names are:
Chris@13 161
Chris@13 162 * `Stmt_Function -> PhpParser\Node\Stmt\Function_`
Chris@13 163 * `Stmt_Expression -> PhpParser\Node\Stmt\Expression`
Chris@13 164
Chris@13 165 The additional `_` at the end of the first class name is necessary, because `Function` is a
Chris@13 166 reserved keyword. Many node class names in this library have a trailing `_` to avoid clashing with
Chris@13 167 a keyword.
Chris@13 168
Chris@13 169 As PHP is a large language there are approximately 140 different nodes. In order to make working
Chris@0 170 with them easier they are grouped into three categories:
Chris@0 171
Chris@0 172 * `PhpParser\Node\Stmt`s are statement nodes, i.e. language constructs that do not return
Chris@0 173 a value and can not occur in an expression. For example a class definition is a statement.
Chris@0 174 It doesn't return a value and you can't write something like `func(class A {});`.
Chris@0 175 * `PhpParser\Node\Expr`s are expression nodes, i.e. language constructs that return a value
Chris@0 176 and thus can occur in other expressions. Examples of expressions are `$var`
Chris@0 177 (`PhpParser\Node\Expr\Variable`) and `func()` (`PhpParser\Node\Expr\FuncCall`).
Chris@0 178 * `PhpParser\Node\Scalar`s are nodes representing scalar values, like `'string'`
Chris@0 179 (`PhpParser\Node\Scalar\String_`), `0` (`PhpParser\Node\Scalar\LNumber`) or magic constants
Chris@0 180 like `__FILE__` (`PhpParser\Node\Scalar\MagicConst\File`). All `PhpParser\Node\Scalar`s extend
Chris@0 181 `PhpParser\Node\Expr`, as scalars are expressions, too.
Chris@0 182 * There are some nodes not in either of these groups, for example names (`PhpParser\Node\Name`)
Chris@0 183 and call arguments (`PhpParser\Node\Arg`).
Chris@0 184
Chris@13 185 The `Node\Stmt\Expression` node is somewhat confusing in that it contains both the terms "statement"
Chris@13 186 and "expression". This node distinguishes `expr`, which is a `Node\Expr`, from `expr;`, which is
Chris@13 187 an "expression statement" represented by `Node\Stmt\Expression` and containing `expr` as a sub-node.
Chris@0 188
Chris@0 189 Every node has a (possibly zero) number of subnodes. You can access subnodes by writing
Chris@0 190 `$node->subNodeName`. The `Stmt\Echo_` node has only one subnode `exprs`. So in order to access it
Chris@0 191 in the above example you would write `$stmts[0]->exprs`. If you wanted to access the name of the function
Chris@0 192 call, you would write `$stmts[0]->exprs[1]->name`.
Chris@0 193
Chris@0 194 All nodes also define a `getType()` method that returns the node type. The type is the class name
Chris@0 195 without the `PhpParser\Node\` prefix and `\` replaced with `_`. It also does not contain a trailing
Chris@0 196 `_` for reserved-keyword class names.
Chris@0 197
Chris@0 198 It is possible to associate custom metadata with a node using the `setAttribute()` method. This data
Chris@0 199 can then be retrieved using `hasAttribute()`, `getAttribute()` and `getAttributes()`.
Chris@0 200
Chris@0 201 By default the lexer adds the `startLine`, `endLine` and `comments` attributes. `comments` is an array
Chris@0 202 of `PhpParser\Comment[\Doc]` instances.
Chris@0 203
Chris@0 204 The start line can also be accessed using `getLine()`/`setLine()` (instead of `getAttribute('startLine')`).
Chris@0 205 The last doc comment from the `comments` attribute can be obtained using `getDocComment()`.
Chris@0 206
Chris@0 207 Pretty printer
Chris@0 208 --------------
Chris@0 209
Chris@0 210 The pretty printer component compiles the AST back to PHP code. As the parser does not retain formatting
Chris@0 211 information the formatting is done using a specified scheme. Currently there is only one scheme available,
Chris@0 212 namely `PhpParser\PrettyPrinter\Standard`.
Chris@0 213
Chris@0 214 ```php
Chris@0 215 use PhpParser\Error;
Chris@0 216 use PhpParser\ParserFactory;
Chris@0 217 use PhpParser\PrettyPrinter;
Chris@0 218
Chris@0 219 $code = "<?php echo 'Hi ', hi\\getTarget();";
Chris@0 220
Chris@0 221 $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
Chris@0 222 $prettyPrinter = new PrettyPrinter\Standard;
Chris@0 223
Chris@0 224 try {
Chris@0 225 // parse
Chris@0 226 $stmts = $parser->parse($code);
Chris@0 227
Chris@0 228 // change
Chris@0 229 $stmts[0] // the echo statement
Chris@0 230 ->exprs // sub expressions
Chris@0 231 [0] // the first of them (the string node)
Chris@0 232 ->value // it's value, i.e. 'Hi '
Chris@0 233 = 'Hello '; // change to 'Hello '
Chris@0 234
Chris@0 235 // pretty print
Chris@0 236 $code = $prettyPrinter->prettyPrint($stmts);
Chris@0 237
Chris@0 238 echo $code;
Chris@0 239 } catch (Error $e) {
Chris@0 240 echo 'Parse Error: ', $e->getMessage();
Chris@0 241 }
Chris@0 242 ```
Chris@0 243
Chris@0 244 The above code will output:
Chris@0 245
Chris@13 246 echo 'Hello ', hi\getTarget();
Chris@0 247
Chris@0 248 As you can see the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
Chris@0 249 again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
Chris@0 250
Chris@0 251 The `prettyPrint()` method pretty prints a statements array. It is also possible to pretty print only a
Chris@0 252 single expression using `prettyPrintExpr()`.
Chris@0 253
Chris@0 254 The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `<?php` tag
Chris@0 255 and handle inline HTML as the first/last statement more gracefully.
Chris@0 256
Chris@13 257 > Read more: [Pretty printing documentation](component/Pretty_printing.markdown)
Chris@13 258
Chris@0 259 Node traversation
Chris@0 260 -----------------
Chris@0 261
Chris@0 262 The above pretty printing example used the fact that the source code was known and thus it was easy to
Chris@0 263 write code that accesses a certain part of a node tree and changes it. Normally this is not the case.
Chris@0 264 Usually you want to change / analyze code in a generic way, where you don't know how the node tree is
Chris@0 265 going to look like.
Chris@0 266
Chris@0 267 For this purpose the parser provides a component for traversing and visiting the node tree. The basic
Chris@0 268 structure of a program using this `PhpParser\NodeTraverser` looks like this:
Chris@0 269
Chris@0 270 ```php
Chris@0 271 use PhpParser\NodeTraverser;
Chris@0 272 use PhpParser\ParserFactory;
Chris@0 273 use PhpParser\PrettyPrinter;
Chris@0 274
Chris@0 275 $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
Chris@0 276 $traverser = new NodeTraverser;
Chris@0 277 $prettyPrinter = new PrettyPrinter\Standard;
Chris@0 278
Chris@0 279 // add your visitor
Chris@0 280 $traverser->addVisitor(new MyNodeVisitor);
Chris@0 281
Chris@0 282 try {
Chris@0 283 $code = file_get_contents($fileName);
Chris@0 284
Chris@0 285 // parse
Chris@0 286 $stmts = $parser->parse($code);
Chris@0 287
Chris@0 288 // traverse
Chris@0 289 $stmts = $traverser->traverse($stmts);
Chris@0 290
Chris@0 291 // pretty print
Chris@0 292 $code = $prettyPrinter->prettyPrintFile($stmts);
Chris@0 293
Chris@0 294 echo $code;
Chris@0 295 } catch (PhpParser\Error $e) {
Chris@0 296 echo 'Parse Error: ', $e->getMessage();
Chris@0 297 }
Chris@0 298 ```
Chris@0 299
Chris@0 300 The corresponding node visitor might look like this:
Chris@0 301
Chris@0 302 ```php
Chris@0 303 use PhpParser\Node;
Chris@0 304 use PhpParser\NodeVisitorAbstract;
Chris@0 305
Chris@0 306 class MyNodeVisitor extends NodeVisitorAbstract
Chris@0 307 {
Chris@0 308 public function leaveNode(Node $node) {
Chris@0 309 if ($node instanceof Node\Scalar\String_) {
Chris@0 310 $node->value = 'foo';
Chris@0 311 }
Chris@0 312 }
Chris@0 313 }
Chris@0 314 ```
Chris@0 315
Chris@0 316 The above node visitor would change all string literals in the program to `'foo'`.
Chris@0 317
Chris@0 318 All visitors must implement the `PhpParser\NodeVisitor` interface, which defines the following four
Chris@0 319 methods:
Chris@0 320
Chris@0 321 ```php
Chris@0 322 public function beforeTraverse(array $nodes);
Chris@0 323 public function enterNode(\PhpParser\Node $node);
Chris@0 324 public function leaveNode(\PhpParser\Node $node);
Chris@0 325 public function afterTraverse(array $nodes);
Chris@0 326 ```
Chris@0 327
Chris@0 328 The `beforeTraverse()` method is called once before the traversal begins and is passed the nodes the
Chris@0 329 traverser was called with. This method can be used for resetting values before traversation or
Chris@0 330 preparing the tree for traversal.
Chris@0 331
Chris@0 332 The `afterTraverse()` method is similar to the `beforeTraverse()` method, with the only difference that
Chris@0 333 it is called once after the traversal.
Chris@0 334
Chris@0 335 The `enterNode()` and `leaveNode()` methods are called on every node, the former when it is entered,
Chris@0 336 i.e. before its subnodes are traversed, the latter when it is left.
Chris@0 337
Chris@0 338 All four methods can either return the changed node or not return at all (i.e. `null`) in which
Chris@0 339 case the current node is not changed.
Chris@0 340
Chris@0 341 The `enterNode()` method can additionally return the value `NodeTraverser::DONT_TRAVERSE_CHILDREN`,
Chris@17 342 which instructs the traverser to skip all children of the current node. To furthermore prevent subsequent
Chris@17 343 visitors from visiting the current node, `NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN` can be used instead.
Chris@0 344
Chris@0 345 The `leaveNode()` method can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
Chris@0 346 case the current node will be removed from the parent array. Furthermore it is possible to return
Chris@0 347 an array of nodes, which will be merged into the parent array at the offset of the current node.
Chris@0 348 I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
Chris@0 349 be `array(A, X, Y, Z, C)`.
Chris@0 350
Chris@0 351 Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
Chris@0 352 class, which will define empty default implementations for all the above methods.
Chris@0 353
Chris@13 354 > Read more: [Walking the AST](component/Walking_the_AST.markdown)
Chris@13 355
Chris@0 356 The NameResolver node visitor
Chris@0 357 -----------------------------
Chris@0 358
Chris@13 359 One visitor that is already bundled with the package is `PhpParser\NodeVisitor\NameResolver`. This visitor
Chris@0 360 helps you work with namespaced code by trying to resolve most names to fully qualified ones.
Chris@0 361
Chris@0 362 For example, consider the following code:
Chris@0 363
Chris@0 364 use A as B;
Chris@0 365 new B\C();
Chris@0 366
Chris@0 367 In order to know that `B\C` really is `A\C` you would need to track aliases and namespaces yourself.
Chris@0 368 The `NameResolver` takes care of that and resolves names as far as possible.
Chris@0 369
Chris@13 370 After running it, most names will be fully qualified. The only names that will stay unqualified are
Chris@0 371 unqualified function and constant names. These are resolved at runtime and thus the visitor can't
Chris@0 372 know which function they are referring to. In most cases this is a non-issue as the global functions
Chris@0 373 are meant.
Chris@0 374
Chris@0 375 Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations
Chris@0 376 that contains the namespaced name instead of only the shortname that is available via `name`.
Chris@0 377
Chris@13 378 > Read more: [Name resolution documentation](component/Name_resolution.markdown)
Chris@13 379
Chris@0 380 Example: Converting namespaced code to pseudo namespaces
Chris@0 381 --------------------------------------------------------
Chris@0 382
Chris@0 383 A small example to understand the concept: We want to convert namespaced code to pseudo namespaces
Chris@0 384 so it works on 5.2, i.e. names like `A\\B` should be converted to `A_B`. Note that such conversions
Chris@0 385 are fairly complicated if you take PHP's dynamic features into account, so our conversion will
Chris@0 386 assume that no dynamic features are used.
Chris@0 387
Chris@0 388 We start off with the following base code:
Chris@0 389
Chris@0 390 ```php
Chris@0 391 use PhpParser\ParserFactory;
Chris@0 392 use PhpParser\PrettyPrinter;
Chris@0 393 use PhpParser\NodeTraverser;
Chris@0 394 use PhpParser\NodeVisitor\NameResolver;
Chris@0 395
Chris@0 396 $inDir = '/some/path';
Chris@0 397 $outDir = '/some/other/path';
Chris@0 398
Chris@0 399 $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
Chris@0 400 $traverser = new NodeTraverser;
Chris@0 401 $prettyPrinter = new PrettyPrinter\Standard;
Chris@0 402
Chris@0 403 $traverser->addVisitor(new NameResolver); // we will need resolved names
Chris@0 404 $traverser->addVisitor(new NamespaceConverter); // our own node visitor
Chris@0 405
Chris@0 406 // iterate over all .php files in the directory
Chris@0 407 $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($inDir));
Chris@0 408 $files = new \RegexIterator($files, '/\.php$/');
Chris@0 409
Chris@0 410 foreach ($files as $file) {
Chris@0 411 try {
Chris@0 412 // read the file that should be converted
Chris@16 413 $code = file_get_contents($file->getPathName());
Chris@0 414
Chris@0 415 // parse
Chris@0 416 $stmts = $parser->parse($code);
Chris@0 417
Chris@0 418 // traverse
Chris@0 419 $stmts = $traverser->traverse($stmts);
Chris@0 420
Chris@0 421 // pretty print
Chris@0 422 $code = $prettyPrinter->prettyPrintFile($stmts);
Chris@0 423
Chris@0 424 // write the converted file to the target directory
Chris@0 425 file_put_contents(
Chris@0 426 substr_replace($file->getPathname(), $outDir, 0, strlen($inDir)),
Chris@0 427 $code
Chris@0 428 );
Chris@0 429 } catch (PhpParser\Error $e) {
Chris@0 430 echo 'Parse Error: ', $e->getMessage();
Chris@0 431 }
Chris@0 432 }
Chris@0 433 ```
Chris@0 434
Chris@0 435 Now lets start with the main code, the `NodeVisitor\NamespaceConverter`. One thing it needs to do
Chris@0 436 is convert `A\\B` style names to `A_B` style ones.
Chris@0 437
Chris@0 438 ```php
Chris@0 439 use PhpParser\Node;
Chris@0 440
Chris@0 441 class NamespaceConverter extends \PhpParser\NodeVisitorAbstract
Chris@0 442 {
Chris@0 443 public function leaveNode(Node $node) {
Chris@0 444 if ($node instanceof Node\Name) {
Chris@13 445 return new Node\Name(str_replace('\\', '_', $node->toString()));
Chris@0 446 }
Chris@0 447 }
Chris@0 448 }
Chris@0 449 ```
Chris@0 450
Chris@0 451 The above code profits from the fact that the `NameResolver` already resolved all names as far as
Chris@0 452 possible, so we don't need to do that. We only need to create a string with the name parts separated
Chris@13 453 by underscores instead of backslashes. This is what `str_replace('\\', '_', $node->toString())` does. (If you want to
Chris@0 454 create a name with backslashes either write `$node->toString()` or `(string) $node`.) Then we create
Chris@0 455 a new name from the string and return it. Returning a new node replaces the old node.
Chris@0 456
Chris@0 457 Another thing we need to do is change the class/function/const declarations. Currently they contain
Chris@0 458 only the shortname (i.e. the last part of the name), but they need to contain the complete name including
Chris@0 459 the namespace prefix:
Chris@0 460
Chris@0 461 ```php
Chris@0 462 use PhpParser\Node;
Chris@0 463 use PhpParser\Node\Stmt;
Chris@0 464
Chris@0 465 class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract
Chris@0 466 {
Chris@0 467 public function leaveNode(Node $node) {
Chris@0 468 if ($node instanceof Node\Name) {
Chris@13 469 return new Node\Name(str_replace('\\', '_', $node->toString()));
Chris@0 470 } elseif ($node instanceof Stmt\Class_
Chris@0 471 || $node instanceof Stmt\Interface_
Chris@0 472 || $node instanceof Stmt\Function_) {
Chris@13 473 $node->name = str_replace('\\', '_', $node->namespacedName->toString());
Chris@0 474 } elseif ($node instanceof Stmt\Const_) {
Chris@0 475 foreach ($node->consts as $const) {
Chris@13 476 $const->name = str_replace('\\', '_', $const->namespacedName->toString());
Chris@0 477 }
Chris@0 478 }
Chris@0 479 }
Chris@0 480 }
Chris@0 481 ```
Chris@0 482
Chris@0 483 There is not much more to it than converting the namespaced name to string with `_` as separator.
Chris@0 484
Chris@0 485 The last thing we need to do is remove the `namespace` and `use` statements:
Chris@0 486
Chris@0 487 ```php
Chris@0 488 use PhpParser\Node;
Chris@0 489 use PhpParser\Node\Stmt;
Chris@13 490 use PhpParser\NodeTraverser;
Chris@0 491
Chris@0 492 class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract
Chris@0 493 {
Chris@0 494 public function leaveNode(Node $node) {
Chris@0 495 if ($node instanceof Node\Name) {
Chris@13 496 return new Node\Name(str_replace('\\', '_', $node->toString()));
Chris@0 497 } elseif ($node instanceof Stmt\Class_
Chris@0 498 || $node instanceof Stmt\Interface_
Chris@0 499 || $node instanceof Stmt\Function_) {
Chris@13 500 $node->name = str_replace('\\', '_', $node->namespacedName->toString();
Chris@0 501 } elseif ($node instanceof Stmt\Const_) {
Chris@0 502 foreach ($node->consts as $const) {
Chris@13 503 $const->name = str_replace('\\', '_', $const->namespacedName->toString());
Chris@0 504 }
Chris@0 505 } elseif ($node instanceof Stmt\Namespace_) {
Chris@0 506 // returning an array merges is into the parent array
Chris@0 507 return $node->stmts;
Chris@0 508 } elseif ($node instanceof Stmt\Use_) {
Chris@13 509 // remove use nodes altogether
Chris@13 510 return NodeTraverser::REMOVE_NODE;
Chris@0 511 }
Chris@0 512 }
Chris@0 513 }
Chris@0 514 ```
Chris@0 515
Chris@0 516 That's all.