本文目录导读:

在PHP项目中使用PHP-Parser(PHP-Parser是一个PHP编写的PHP语法解析器),主要步骤如下:
安装
通过Composer安装:
composer require nikic/php-parser
基本使用流程
<?php
require 'vendor/autoload.php';
use PhpParser\Error;
use PhpParser\NodeDumper;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;
// 1. 创建解析器
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
// 2. 要解析的代码
$code = <<<'CODE'
<?php
function test($a, $b = 0) {
return $a + $b;
}
echo test(1, 2);
CODE;
// 3. 解析代码为AST(抽象语法树)
try {
$statements = $parser->parse($code);
} catch (Error $error) {
echo "Parse error: {$error->getMessage()}\n";
return;
}
// 4. 查看AST结构
$dumper = new NodeDumper;
echo $dumper->dump($statements) . "\n\n";
// 5. 修改AST(示例:将所有函数名改为大写)
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new class extends PhpParser\NodeVisitorAbstract {
public function leaveNode(PhpParser\Node $node) {
if ($node instanceof PhpParser\Node\Expr\FuncCall) {
$node->name = new PhpParser\Node\Name(strtoupper((string)$node->name));
}
}
});
$modifiedStatements = $traverser->traverse($statements);
// 6. 重新生成代码
$prettyPrinter = new PrettyPrinter\Standard;
echo $prettyPrinter->prettyPrintFile($modifiedStatements);
常见应用场景
静态分析
// 统计函数调用次数
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new class extends PhpParser\NodeVisitorAbstract {
private $functionCalls = [];
public function enterNode(PhpParser\Node $node) {
if ($node instanceof PhpParser\Node\Expr\FuncCall) {
$name = (string)$node->name;
if (!isset($this->functionCalls[$name])) {
$this->functionCalls[$name] = 0;
}
$this->functionCalls[$name]++;
}
}
public function getFunctionCalls() {
return $this->functionCalls;
}
});
$traverser->traverse($statements);
print_r($traverser->visitors[0]->getFunctionCalls());
代码转换(自动添加类型声明)
$traverser->addVisitor(new class extends PhpParser\NodeVisitorAbstract {
public function leaveNode(PhpParser\Node $node) {
if ($node instanceof PhpParser\Node\Stmt\Function_) {
// 添加返回类型
$node->returnType = new PhpParser\Node\Identifier('int');
}
}
});
生成文档
function extractDocComments($statements) {
foreach ($statements as $stmt) {
if ($stmt instanceof PhpParser\Node\Stmt\Function_) {
$name = $stmt->name;
$docComment = $stmt->getDocComment();
echo "Function: $name\n";
if ($docComment) {
echo "DocComment: " . $docComment->getText() . "\n\n";
}
}
}
}
高级用法:自定义节点类型
// 解析JSON配置文件并转换为PHP数组
class JsonConfigParser {
public static function parse(string $json): string {
$config = json_decode($json, true);
$ast = [];
foreach ($config as $key => $value) {
$ast[] = new PhpParser\Node\Expr\Assign(
new PhpParser\Node\Expr\ArrayDimFetch(
new PhpParser\Node\Expr\Variable('config'),
new PhpParser\Node\Scalar\String_($key)
),
self::valueToNode($value)
);
}
$printer = new PrettyPrinter\Standard;
return $printer->prettyPrint($ast);
}
private static function valueToNode($value): PhpParser\Node\Expr {
if (is_array($value)) {
$items = [];
foreach ($value as $k => $v) {
$items[] = new PhpParser\Node\Expr\ArrayItem(
self::valueToNode($v),
is_string($k) ? new PhpParser\Node\Scalar\String_($k) : null
);
}
return new PhpParser\Node\Expr\Array_($items);
} elseif (is_string($value)) {
return new PhpParser\Node\Scalar\String_($value);
} elseif (is_int($value)) {
return new PhpParser\Node\Scalar\LNumber($value);
} elseif (is_bool($value)) {
return new PhpParser\Node\Expr\ConstFetch(
new PhpParser\Node\Name($value ? 'true' : 'false')
);
}
return new PhpParser\Node\Expr\ConstFetch(new PhpParser\Node\Name('null'));
}
}
性能优化建议
// 1. 缓存解析结果
$cacheFile = '/tmp/ast_cache_' . md5($filePath);
if (file_exists($cacheFile)) {
$statements = unserialize(file_get_contents($cacheFile));
} else {
$statements = $parser->parse(file_get_contents($filePath));
file_put_contents($cacheFile, serialize($statements));
}
// 2. 批量处理多个文件
$files = ['file1.php', 'file2.php', 'file3.php'];
foreach ($files as $file) {
$statements = $parser->parse(file_get_contents($file));
// 处理...
}
常见错误处理
try {
$statements = $parser->parse($code);
} catch (PhpParser\Error $e) {
echo "解析错误: " . $e->getMessage() . "\n";
echo "行号: " . $e->getStartLine() . "\n";
} catch (Exception $e) {
echo "其他错误: " . $e->getMessage() . "\n";
}
实践建议
- 版本选择:根据你的PHP版本选择合适的解析器模式(PREFER_PHP5、PREFER_PHP7、ONLY_PHP5、ONLY_PHP7)
- 单元测试:对复杂的AST操作编写单元测试
- 内存管理:处理大文件时注意内存使用,可以考虑分批处理
- 错误处理:始终使用try-catch捕获解析错误
PHP-Parser功能强大,广泛应用于代码分析、自动重构、代码生成、静态分析工具(如PHPStan、Psalm)等场景。