PHP 使用 nikic/php-parser 处理 AST

date
Jun 3, 2021
slug
php-nikic-php-parser-ast
status
Published
tags
PHP
summary
讨论 nikic/php-parser 如何处理 AST
type
Post

先来熟悉 php-parser 的 API

nikic/PHP-Parser 可以解析 PHP 代码并生成 AST,还支持修改 AST 再还原成PHP源码,从而实现元编程,可用来做 AOP 和静态代码检查等。Swoft 框架中 AOP 也是基于 PHP-parser 开发的。
 
首先使用 composer 安装 php-parser
在代码中引入 autoload.php,开始测试代码
打印出结果:
上面打印的数组中分别是:
  • Stmt_Function -> PhpParser\Node\Stmt\Function_
  • Stmt_Expression -> PhpParser\Node\Stmt\Expression
Function_ 有个 _ 后缀是因为 Function 本身是保留字,包中还有很多命名带有_ 也都是这个原因。
Node 的类型说明:
  • PhpParser\Node\Stmts are statement nodes, i.e. language constructs that do not return a value and can not occur in an expression. For example a class definition is a statement. It doesn't return a value and you can't write something like func(class A {});.
  • PhpParser\Node\Exprs are expression nodes, i.e. language constructs that return a value and thus can occur in other expressions. Examples of expressions are $var (PhpParser\Node\Expr\Variable) and func() (PhpParser\Node\Expr\FuncCall).
  • PhpParser\Node\Scalars are nodes representing scalar values, like 'string' (PhpParser\Node\Scalar\String_), 0 (PhpParser\Node\Scalar\LNumber) or magic constants like __FILE__ (PhpParser\Node\Scalar\MagicConst\File). All PhpParser\Node\Scalars extend PhpParser\Node\Expr, as scalars are expressions, too.
  • There are some nodes not in either of these groups, for example names (PhpParser\Node\Name) and call arguments (PhpParser\Node\Arg).
 
访问并修改 Node:
打印结果:
 
遍历 AST 中的 Node:
遍历 AST 需要指定一个访问器,需实现几个方法,beforeTraverse 和 afterTraverse 是在开始遍历前和结束遍历后执行一次,enterNode 和 leaveNode 是每遍历到一个 Node 时执行一次。
输出:
 
输出修改后的 PHP 代码,即 Pretty Print
输出:
函数体被清空了,并且第二个语句 printLine 中的参数被替换了。
 
有了这种能力,结合一些注释标注等,就可以在 PHP 代码在执行之前动态修改带有指定特征的 PHP代码的行为。
 

使用 PHP-parser 重写 PHP 类代码实现AOP:

该 AOP 增强的效果是在字符串后面增加一个叹号 !
 
入口 aop.php:
PHP-Parser 的访问器 ProxyVisitor.php
被代理的类 Test.php
执行后,被增强的结果类为:
执行结果:
 

Swoft 框架中的 AOP 实现原理

swoft 的 aop 也是基于 php-parser 来实现的,由于懒的搞 phpunit,在本是 testcase 的类上直接改代码手动调试了:
newClassName 方法如下:
得到类名,即可 new 之,按照原类的方法签名方式调用,即可得到代理后的效果。
 

© 菜皮 2020 - 2024