zoukankan      html  css  js  c++  java
  • 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 AppAop;
    
    use PhpParserNode;
    use PhpParserNodeExprClosure;
    use PhpParserNodeExprFuncCall;
    use PhpParserNodeExprMethodCall;
    use PhpParserNodeExprVariable;
    use PhpParserNodeName;
    use PhpParserNodeParam;
    use PhpParserNodeScalarString_;
    use PhpParserNodeStmtClass_;
    use PhpParserNodeStmtClassMethod;
    use PhpParserNodeStmtReturn_;
    use PhpParserNodeStmtTraitUse;
    use PhpParserNodeFinder;
    use PhpParserNodeVisitorAbstract;
    
    /**
     * PHP Parser在Aop编程中的使用
     *
     * 流程:
     * 1、当我们拿到节点是类时,我们重置这个类,让新建的类继承这个类。
     * 2、当我们拿到的节点是类方法时,我们使用proxyCall来重写方法。
     * 3、当遍历完成之后,给类加上我们定义好的AopTrait。
     *
     * NodeVisitor接口调用顺序:beforeTraverse -> enterNode -> leaveNode -> afterTraverse
     *
     * Class ProxyVisitor
     * @package AppAop
     */
    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 PhpParserNodeStmtTraitUse
         */
        private function getAopTraitUseNode(): TraitUse
        {
            // Use AopTrait trait use node
            return new TraitUse([new Name('AppAopAopTrait')]);
        }
    
        /**
         * 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 AppAop
     */
    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 AppHttpControllers;
    
    use PhpParserParserFactory;
    use PhpParserNodeDumper;
    use PhpParserNodeTraverser;
    use AppAopProxyVisitor;
    use PhpParserPrettyPrinterStandard;
    
    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

  • 相关阅读:
    mysql存储过程的优点
    MySQL复制
    优化数据库的方法
    MySQL表分区
    Http请求
    memcache和redis的区别
    触发器实现原理
    PHP常见数组函数与实例
    git 打包指定提交过的代码
    发送HTTP请求方法
  • 原文地址:https://www.cnblogs.com/cshaptx4869/p/10654624.html
Copyright © 2011-2022 走看看