您好,登錄后才能下訂單哦!
這篇文章主要講解了“php-parser在Aop編程中的使用方法”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“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(); } }
感謝各位的閱讀,以上就是“php-parser在Aop編程中的使用方法”的內容了,經過本文的學習后,相信大家對php-parser在Aop編程中的使用方法這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。