PHP-Parser入門した
最近「PHPのコードをパースしていい感じにしたい」ということがあった。その時にPHP-Parserについて調べたので、使い方などを軽くメモしておく。
PHP-Parserとは
このライブラリのこと。
PHPのコードを静的解析して抽象構文木(AST)を生成し、そのASTに対して任意の操作を行ったり、PHPのコードに戻したりできる。ASTについてはインターネット上にいろんな記事があるのでそちらを参照してほしい。
使用例
【注意】以下のサンプルコードは、執筆時点で最新のv4.10.4を使用したものである。
PHPのコードをASTにして、何もせずPHPのコードに戻す
<?php require_once __DIR__ . '/vendor/autoload.php'; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter\Standard; $code = file_get_contents('./Hoge.php'); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); $ast = $parser->parse($code); $printer = new Standard; $result = $printer->prettyPrintFile($ast); var_dump($result);
この例だと、空行が削除されるなどの余計なフォーマット処理が走ってしまう。v4.0からそれを回避する機能が実験的に追加されたようで、以下のように書き換えできる。
<?php require_once __DIR__ . '/vendor/autoload.php'; use PhpParser\Lexer; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor; use PhpParser\Parser; use PhpParser\PrettyPrinter\Standard; $code = file_get_contents('./Hoge.php'); $lexer = new Lexer\Emulative([ 'usedAttributes' => [ 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', ], 'phpVersion' => Lexer\Emulative::PHP_7_4, ]); $parser = new Parser\Php7($lexer); $oldStmts = $parser->parse($code); $oldTokens = $lexer->getTokens(); $traverser = new NodeTraverser; $traverser->addVisitor(new NodeVisitor\CloningVisitor); $newStmts = $traverser->traverse($oldStmts); $printer = new Standard; $result = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens); var_dump($result);
PHPのコードをASTにして、一部コードを書き換えてからPHPのコードに戻す
<?php class Hoge { public function getMessage() : string { return 'hoge'; } }
このgetMessage
の戻り値をhoge
からpoyo
に変えたい場合は、以下のようなコードを用意すれば良い。
<?php require_once __DIR__ . '/vendor/autoload.php'; use PhpParser\Lexer; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; use PhpParser\Parser; use PhpParser\PrettyPrinter\Standard; $code = file_get_contents('./Hoge.php'); $lexer = new Lexer\Emulative([ 'usedAttributes' => [ 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', ], 'phpVersion' => Lexer\Emulative::PHP_7_4, ]); $parser = new Parser\Php7($lexer); $oldStmts = $parser->parse($code); $oldTokens = $lexer->getTokens(); $traverser = new NodeTraverser; $traverser->addVisitor(new NodeVisitor\CloningVisitor); $newStmts = $traverser->traverse($oldStmts); $traverser = new NodeTraverser; $traverser->addVisitor(new class extends NodeVisitorAbstract { public function enterNode(Node $node) { if ($node instanceof Node\Stmt\Return_ && $node->expr instanceof Node\Scalar\String_ && $node->expr->value === 'hoge' ) { $node->expr->value = 'poyo'; } } }); $newStmts = $traverser->traverse($newStmts); $printer = new Standard; $result = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens); var_dump($result);
本質は$traverser->addVisitor(new class ...
で処理を差し込んでいる部分のみ。NodeTraverserを用いることでノードの走査中に実行したい処理を差し込むことができる。処理の実行タイミングは4種類あり、enterNode
はその一種。
ref: https://github.com/nikic/PHP-Parser/blob/master/doc/component/Walking_the_AST.markdown#node-visitors
2回に分けて走査しているが、これは1回目で各ノードの初期状態を複製しておくため。最後のコード出力のprintFormatPreserving
で必要。
さいごに
PHP-Parserで最初にやりそうなことを使用例と共にメモしてみた。ここで書いた内容が何となく分かれば、あとは公式ドキュメントを見ながらいろいろ試せるようになると思う。