PHP-Parser 实战:精巧的 AST 手术刀

前言

这几天初步了解了一下 PHP-Parser,它是一个由 PHP 语言编写的 PHP 解析器,支持将残缺的 PHP 代码转换为 AST、对 AST 进行操作以及将 AST 转换为格式友好的 PHP 源代码。功能可以说是十分强大且灵活。前文提到的 AST 即抽象语法树,是源代码语法结构的一种抽象表示,可以认为 AST 与其对应的 PHP 源代码是等价的。

项目地址:nikic/PHP-Parser,十分感谢 nikic 大佬以及其他 Contributors 为我们提供了如此方便强大的库。

使用 composer 安装:

php composer.phar require nikic/php-parser

生成 AST

我们需要利用 AST 便利的特性,当然就需要先把原生 PHP 代码转换为一棵 AST。

use PhpParser\Error;
use PhpParser\NodeDumper;
use PhpParser\ParserFactory;

$code = <<<'CODE'
<?php
function test() {
    var_dump(0xaa);
}
test();
CODE;

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}

$dumper = new NodeDumper;
echo $dumper->dump($ast) . "\n";

这就是一个最简单的例子,它可以生成 $code 中代码所对应的 AST,同时通过 NodeDumper,我们可以轻松直观地看到它的结构。

操作 AST

PHP-Parser 提供了 NodeVisitorNodeFinder 两种遍历节点的接口,后者是前者的一种简洁用法,本质上它们都需要使用 NodeTraverser。这里我们以适用范围更广的 NodeVisitor 举例。

AST 就像是一把精巧的手术刀,许多大规模改动源代码的问题都可以通过它简便地解决。操作 AST 时就像是在对源代码动手术,以节点为单位批量解决许多看似难以解决的问题。

例如,我们需要把代码中所有的十六进制数字换成十进制书写,便于维护,在源代码很大时这个工作将会很困难,但利用 PHP-Parser 即可轻松处理。我们用 NodeVisitor 处理一下:

use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node\Scalar\LNumber;
$traverser = new NodeTraverser();
$traverser->addVisitor(new class extends NodeVisitorAbstract {
    public function leaveNode(Node $node) {
        if ($node instanceof LNumber) { // 十六进制数字修复为十进制
            return new LNumber($node->value,array('kind'=>LNumber::KIND_DEC));
        }
    }
});
$ast = $traverser->traverse($ast);

对于源代码中的数字,PHP-Parser 将其解析为 LNumber 节点,我们重新创建一个 kind 为十进制、值与原先相同的 LNumber 节点即可。

leaveNode 方法将在 NodeVisitor 离开节点时执行,返回值将改变该节点的值。对于 Expression 节点,返回常量 NodeTraverser::REMOVE_NODE 可以移除该节点。

生成代码

PHP-Parser 在操作完毕后,一般需要将抽象的数据结构 AST 转换回具体的源代码,此时可以使用 PrettyPrinter。经过 PrettyPrinter 处理后,可以得到美化(当然也可以是自定义格式)后的源代码。

$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$newCode = $prettyPrinter->prettyPrintFile($ast);
最后修改:2020 年 01 月 18 日 04 : 51 PM
欢迎投食喵 ~

发表评论