php-parser在Aop编程中的使用
在laravel下使用php-parser实现aop
composer require nikic/php-parser
Test.php
<?php
/**
* Created by PhpStorm.
* User: CSH
* Date: 2019/4/4
* Time: 11:26
*/ namespace app; /**
* 为该类创建代理,并植入切面 埋点
* 使用parser生成对应的语法树,然后主动修改方法体内的逻辑
*
* Class Test
* @package app
*/
class Test
{
// do something before
// do something
// do something after public function show()
{
return 'Hello World';
} public function say()
{
return 'I Can Fly';
} }
ProxyVisitor.php
<?php
/**
* Created by PhpStorm.
* User: CSH
* Date: 2019/4/4
* Time: 11:16
*/ namespace App\Aop; use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\NodeFinder;
use PhpParser\NodeVisitorAbstract; /**
* PHP Parser在Aop编程中的使用
*
* 流程:
* 1、当我们拿到节点是类时,我们重置这个类,让新建的类继承这个类。
* 2、当我们拿到的节点是类方法时,我们使用proxyCall来重写方法。
* 3、当遍历完成之后,给类加上我们定义好的AopTrait。
*
* NodeVisitor接口调用顺序:beforeTraverse -> enterNode -> leaveNode -> afterTraverse
*
* Class ProxyVisitor
* @package App\Aop
*/
class ProxyVisitor extends NodeVisitorAbstract
{
protected $className; protected $proxyId; public function __construct($className, $proxyId)
{
$this->className = $className;
$this->proxyId = $proxyId;
} public function getProxyClassName()
{
return \basename(str_replace('\\', '/', $this->className)).'_'.$this->proxyId;
} public function getClassName()
{
return '\\'.$this->className.'_'.$this->proxyId;
} /**
* @return \PhpParser\Node\Stmt\TraitUse
*/
private function getAopTraitUseNode(): TraitUse
{
// Use AopTrait trait use node
return new TraitUse([new Name('\App\Aop\AopTrait')]);
} /**
* Called when leaving a node
* 把类方法里的逻辑重置掉
*
* @param Node $node
* @return int|null|Node|Node[]|Class_|ClassMethod
*/
public function leaveNode(Node $node)
{
// Proxy Class
if ($node instanceof Class_) {
// Create proxy class base on parent class
return new Class_($this->getProxyClassName(), [
'flags' => $node->flags,
'stmts' => $node->stmts,
'extends' => new Name('\\'.$this->className),
]);
}
// Rewrite public and protected methods, without static methods
if ($node instanceof ClassMethod && !$node->isStatic() && ($node->isPublic() || $node->isProtected())) {
$methodName = $node->name->toString();
// Rebuild closure uses, only variable
$uses = [];
foreach ($node->params as $key => $param) {
if ($param instanceof Param) {
$uses[$key] = new Param($param->var, null, null, true);
}
}
$params = [
// Add method to an closure
new Closure([
'static' => $node->isStatic(),
'uses' => $uses,
'stmts' => $node->stmts,
]),
new String_($methodName),
new FuncCall(new Name('func_get_args')),
];
$stmts = [
new Return_(new MethodCall(new Variable('this'), '__proxyCall', $params))
];
$returnType = $node->getReturnType();
if ($returnType instanceof Name && $returnType->toString() === 'self') {
$returnType = new Name('\\'.$this->className);
}
return new ClassMethod($methodName, [
'flags' => $node->flags,
'byRef' => $node->byRef,
'params' => $node->params,
'returnType' => $returnType,
'stmts' => $stmts,
]);
}
} /**
* Called once after traversal
* 把AopTrait扔到类里
*
* @param array $nodes
* @return array|null|Node[]
*/
public function afterTraverse(array $nodes)
{
$addEnhancementMethods = true;
$nodeFinder = new NodeFinder();
$nodeFinder->find($nodes, function (Node $node) use (&$addEnhancementMethods) {
if ($node instanceof TraitUse) {
foreach ($node->traits as $trait) {
// Did AopTrait trait use ?
if ($trait instanceof Name && $trait->toString() === '\\App\\Aop\\AopTrait') {
$addEnhancementMethods = false;
break;
}
}
}
});
// Find Class Node and then Add Aop Enhancement Methods nodes and getOriginalClassName() method
$classNode = $nodeFinder->findFirstInstanceOf($nodes, Class_::class);
$addEnhancementMethods && array_unshift($classNode->stmts, $this->getAopTraitUseNode());
return $nodes;
}
} /**
* 切面
*
* Trait AopTrait
* @package App\Aop
*/
trait AopTrait
{
/**
* AOP proxy call method
*
* @param \Closure $closure
* @param string $method
* @param array $params
* @return mixed|null
* @throws \Throwable
*/
public function __proxyCall(\Closure $closure, string $method, array $params)
{
$res = $closure(...$params);
if (is_string($res)) {
$res .= ' !!!';
}
return $res;
}
}
AopController.php
<?php namespace App\Http\Controllers; use PhpParser\ParserFactory;
use PhpParser\NodeDumper;
use PhpParser\NodeTraverser;
use App\Aop\ProxyVisitor;
use PhpParser\PrettyPrinter\Standard; class AopController extends Controller
{
public function index()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse(file_get_contents(base_path().'/app/Test.php')); // 把parser代码后的语法树(对象)转为字符串形式
// $dumper = new NodeDumper();
// dd($dumper->dump($ast)); $className = 'App\\Test';
$proxyId = uniqid();
$visitor = new ProxyVisitor($className, $proxyId); $traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
// 使用已注册的访问者遍历节点数组,返回遍历节点数组
$proxyAst = $traverser->traverse($ast);
if (!$proxyAst) {
throw new \Exception(sprintf('Class %s AST optimize failure', $className));
}
$printer = new Standard();
// 打印一个节点数组
$proxyCode = $printer->prettyPrint($proxyAst); // dd($proxyCode); eval($proxyCode);
$class = $visitor->getClassName();
$bean = new $class();
echo $bean->show();
}
}
参考:
https://learnku.com/articles/14387/aop-design-rewrite-the-php-class-using-php-parser
php-parser在Aop编程中的使用的更多相关文章
- Java基础-SSM之Spring的AOP编程
Java基础-SSM之Spring的AOP编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Spring的本质说白了就是动态代理,接下来我们会体验AOP的用法.它是对OOP的 ...
- Spring入门3.AOP编程
Spring入门3.AOP编程 代码下载: 链接: http://pan.baidu.com/s/11mYEO 密码: x7wa 前言: 前面学习的知识是Spring在Java项目中的IoC或DJ,这 ...
- 聊Javascript中的AOP编程
Duck punch 我们先不谈AOP编程,先从duck punch编程谈起. 如果你去wikipedia中查找duck punch,你查阅到的应该是monkey patch这个词条.根据解释,Mon ...
- JavaEE开发之Spring中的依赖注入与AOP编程
上篇博客我们系统的聊了<JavaEE开发之基于Eclipse的环境搭建以及Maven Web App的创建>,并在之前的博客中我们聊了依赖注入的相关东西,并且使用Objective-C的R ...
- .NET Core中实现AOP编程
AOP全称Aspect Oriented Progarmming(面向切面编程),其实AOP对ASP.NET程序员来说一点都不神秘,你也许早就通过Filter来完成一些通用的功能,例如你使用Autho ...
- 在.NET Core中三种实现“可插拔”AOP编程方式(附源码)
一看标题肯定会联想到使用动态编织的方式实现AOP编程,不过这不是作者本文讨论的重点. 本文讨论另外三种在netcore中可实现的方式,Filter(过滤器,严格意义上它算是AOP方式),Dynamic ...
- 聊聊Javascript中的AOP编程
Duck punch 我们先不谈AOP编程,先从duck punch编程谈起. 如果你去wikipedia中查找duck punch,你查阅到的应该是monkey patch这个词条.根据解释,Mon ...
- Spring AOP——Spring 中面向切面编程
前面两篇文章记录了 Spring IOC 的相关知识,本文记录 Spring 中的另一特性 AOP 相关知识. 部分参考资料: <Spring实战(第4版)> <轻量级 JavaEE ...
- (转).NET Core中实现AOP编程
原文地址:https://www.cnblogs.com/xiandnc/p/10088159.html AOP全称Aspect Oriented Progarmming(面向切面编程),其实AOP对 ...
随机推荐
- Python脚本之安装linux源码包-Jenkins
最近开始学Python,按照网上的教程,写了一个Python脚本下载Jenkins并运行的脚本,很简单. 首先使用vi命令编辑一个新文件auto_built_jenkins.py(关于vi的使用可以见 ...
- 7、LwIP协议规范翻译——网络接口
7.网络接口 在lwIP中,物理网络硬件设备驱动是由类似于BSD网络接口结构表示.网络接口结构如图5所示.网络接口被保存在一个全局的链表中,通过结构体中的next指针来完成链表的链接. 每个网络接口都 ...
- bugfree3.0.1-BUG解决方案修改
该篇内容来自文档“masterBugFree2.pdf”,记录在这里. 1.如何将解决方案改为中文 在\Bugfree\Lang\ZH_CN_UTF-8 \_COMMON.php 文件中做如下修改/* ...
- python基础(6)-深浅拷贝
赋值 字符串和数字 # id()函数可以获取变量在内存中的地址标识 num1 = 2; num2 = 2; print(id(num1)) # result:8791124202560 print(i ...
- ORA-39006错误原因及解决办法
使用impdp导出数据时碰到ora-39006错误,错误提示如下所示: ORA-39006: internal error ORA-39213: Metadata processing is not ...
- 构建 Owin 中间件 来获取客户端IP地址
Not so long ago, we discussed on this blog the possible ways of retrieving the client’s IP address i ...
- OGG学习笔记05-OGG的版本
刚接触OGG的时候,很容易被众多的版本搞晕,虽然官方有提供各版本对应认证OS和DB的表格. 个人认为一个比较简单的方式,是直接去edelivery.oracle.com下载OGG,选定一个大版本后,这 ...
- Mybatis(二)总结
1. 输入映射(就是映射文件中可以传入哪些参数类型) 1)基本类型 2)pojo类型 3)Vo类型2. 输出映射(返回的结果集可以有哪些类型) 1)基本类型 2)pojo类型 3)List类型3. 动 ...
- AWS是怎么改写 MySQL的?
五倍吞吐量的提升,跨可用区的六副本,低于一分钟的宕机恢复,兼容 MySQL协议,这是 AWS 推出 Aurora 数据库时给出的数据. 这种量级的提升不可能是小修小补,大都是在架构上有了变革性的突破才 ...
- tf.InteractiveSession()和tf.Session()
tf.InteractiveSession()适合用于python交互环境 tf.Session()适合用于源代码中 1.tf.InteractiveSession() 直接用eval()就可以直接获 ...