最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

PHP?parser重寫PHP類使用示例詳解

 更新時間:2023年09月08日 14:05:15   作者:李銘昕  
這篇文章主要為大家介紹了PHP?parser重寫PHP類使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

最近一直在研究 Swoft 框架,框架核心當然是 Aop 切面編程,所以想把這部分的心得記下來,以供后期查閱。

Swoft 新版的 Aop 設計建立在 PHP Parser 上面。所以這片文章,主要介紹一下 PHP Parser 在 Aop 編程中的使用。

Test 類

簡單的來講,我們想在某些類的方法上進行埋點,比如下面的 Test 類。

class Test {
    public function get() {
            // do something
        }
}

我們想讓它的 get 方法變成以下的樣子

class Test {
    public function get() {
            // do something before
            // do something
                // do something after
        }
}

最簡單的設計就是,我們使用 parser 生成對應的語法樹,然后主動修改方法體內的邏輯。

接下來,我們就是用 PHP Parser 來搞定這件事。

首先我們先定一個 ProxyVisitor

Visitor 有四個方法,其中

  • beforeTraverse () 方法用于遍歷之前,通常用來在遍歷前對值進行重置。
  • afterTraverse () 方法和(1)相同,唯一不同的地方是遍歷之后才觸發(fā)。
  • enterNode () 和 leaveNode () 方法在對每個節(jié)點訪問時觸發(fā)。
<?php
namespace App\Aop;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node;
class ProxyVisitor extends NodeVisitorAbstract
{
? ? public function leaveNode(Node $node)
? ? {
? ? }
? ? public function afterTraverse(array $nodes)
? ? {
? ? }
}

我們要做的就是重寫 leaveNode,讓我們遍歷語法樹的時候,把類方法里的邏輯重置掉。另外就是重寫 afterTraverse 方法,讓我們遍歷結束之后,把我們的 AopTrait 扔到類里。AopTrait 就是我們賦予給類的,切面編程的能力。

創(chuàng)建一個測試類

首先,我們先創(chuàng)建一個測試類,來看看 parser 生成的語法樹是什么樣子的

namespace App;
class Test
{
? ? public function show()
? ? {
? ? ? ? return 'hello world';
? ? }
}
use PhpParser\ParserFactory;
use PhpParser\NodeDumper;
$file = APP_PATH . '/Test.php';
$code = file_get_contents($file);
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse($code);
$dumper = new NodeDumper();
echo $dumper->dump($ast) . "\n";
結果樹如下
array(
? ? 0: Stmt_Namespace(
? ? ? ? name: Name(
? ? ? ? ? ? parts: array(
? ? ? ? ? ? ? ? 0: App
? ? ? ? ? ? )
? ? ? ? )
? ? ? ? stmts: array(
? ? ? ? ? ? 0: Stmt_Class(
? ? ? ? ? ? ? ? flags: 0
? ? ? ? ? ? ? ? name: Identifier(
? ? ? ? ? ? ? ? ? ? name: Test
? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? extends: null
? ? ? ? ? ? ? ? implements: array(
? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? stmts: array(
? ? ? ? ? ? ? ? ? ? 0: Stmt_ClassMethod(
? ? ? ? ? ? ? ? ? ? ? ? flags: MODIFIER_PUBLIC (1)
? ? ? ? ? ? ? ? ? ? ? ? byRef: false
? ? ? ? ? ? ? ? ? ? ? ? name: Identifier(
? ? ? ? ? ? ? ? ? ? ? ? ? ? name: show
? ? ? ? ? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? ? ? ? ? params: array(
? ? ? ? ? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? ? ? ? ? returnType: null
? ? ? ? ? ? ? ? ? ? ? ? stmts: array(
? ? ? ? ? ? ? ? ? ? ? ? ? ? 0: Stmt_Return(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? expr: Scalar_String(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? value: hello world
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? )
? ? ? ? ? ? )
? ? ? ? )
? ? )
)

語法樹的具體含義,我就不贅述了,感興趣的同學直接去看一下 PHP Parser 的文檔吧。(其實我也沒全都看完。。。大體知道而已,哈哈哈)

接下來重寫我們的 ProxyVisitor

<?php
namespace App\Aop;
use PhpParser\NodeVisitorAbstract;
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;
class ProxyVisitor extends NodeVisitorAbstract
{
? ? protected $className;
? ? protected $proxyId;
? ? public function __construct($className, $proxyId)
? ? {
? ? ? ? $this->className = $className;
? ? ? ? $this->proxyId = $proxyId;
? ? }
? ? public function getProxyClassName(): string
? ? {
? ? ? ? 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')]);
? ? }
? ? 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,
? ? ? ? ? ? ]);
? ? ? ? }
? ? }
? ? 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
{
? ? /**
? ? ?* 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)
? ? {
? ? ? ? return $closure(...$params);
? ? }
}

當我們拿到節(jié)點是類時,我們重置這個類,讓新建的類繼承這個類。

當我們拿到的節(jié)點是類方法時,我們使用 proxyCall 來重寫方法。

當遍歷完成之后,給類加上我們定義好的 AopTrait。

執(zhí)行

接下來,讓我們執(zhí)行以下第二個 DEMO

use PhpParser\ParserFactory;
use PhpParser\NodeTraverser;
use App\Aop\ProxyVisitor;
use PhpParser\PrettyPrinter\Standard;
$file = APP_PATH . '/Test.php';
$code = file_get_contents($file);
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse($code);
$traverser = new NodeTraverser();
$className = 'App\\Test';
$proxyId = uniqid();
$visitor = new ProxyVisitor($className, $proxyId);
$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);
echo $proxyCode;

結果如下

namespace App;
class Test_5b495d7565933 extends \App\Test
{
? ? use \App\Aop\AopTrait;
? ? public function show()
? ? {
? ? ? ? return $this->__proxyCall(function () {
? ? ? ? ? ? return 'hello world';
? ? ? ? }, 'show', func_get_args());
? ? }
}

這樣就很有趣了,我們可以賦予新建的類一個新的方法,比如 getOriginClassName。然后我們在 proxyCall 中,就可以根據(jù) getOriginClassName 和 $method 拿到方法的精確 ID,在這基礎之上,我們可以做很多東西,比如實現(xiàn)一個方法緩存。

我這里呢,只給出一個最簡單的示例,就是當返回值為 string 的時候,加上個嘆號。

修改一下我們的代碼

namespace 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;
? ? }
}

以及在我們的調用代碼后面加上以下代碼

eval($proxyCode);
$class = $visitor->getClassName();
$bean = new $class();
echo $bean->show();

結果當然和我們預想的那樣,打印出了

hello world!

以上設計來自 Swoft 開發(fā)組 swoft-component,我只是個懶惰的搬運工,有興趣的可以去看一下。

以上就是PHP parser重寫PHP類使用示例詳解的詳細內容,更多關于PHP parser重寫PHP類的資料請關注腳本之家其它相關文章!

相關文章

  • 用 Composer構建自己的 PHP 框架之基礎準備

    用 Composer構建自己的 PHP 框架之基礎準備

    這篇文章主要介紹了用 Composer構建自己的 PHP 框架的基礎準備工作,其實就是各種基礎知識,想自己搭建php框架的童鞋可要看仔細了
    2014-10-10
  • php+ajax注冊實時驗證功能

    php+ajax注冊實時驗證功能

    我們在網(wǎng)站上面注冊時,在輸入用戶名時,首先要進行無刷新驗證,這篇文章主要為大家詳細介紹了php+ajax注冊實時驗證功能,感興趣的小伙伴們可以參考一下
    2016-07-07
  • thinkphp視圖模型查詢提示ERR: 1146:Table ''db.pr_order_view'' doesn''t exist的解決方法

    thinkphp視圖模型查詢提示ERR: 1146:Table ''db.pr_order_view'' doesn''

    這篇文章主要介紹了thinkphp視圖模型查詢提示ERR: 1146:Table 'db.pr_order_view' doesn't exist的解決方法,對于ThinkPHP初學者來說有一定的借鑒價值,需要的朋友可以參考下
    2014-10-10
  • php讀取qqwry.dat ip地址定位文件的類實例代碼

    php讀取qqwry.dat ip地址定位文件的類實例代碼

    下面小編就為大家?guī)硪黄猵hp讀取qqwry.dat ip地址定位文件的類實例代碼。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-11-11
  • Laravel創(chuàng)建數(shù)據(jù)庫表結構的例子

    Laravel創(chuàng)建數(shù)據(jù)庫表結構的例子

    今天小編就為大家分享一篇Laravel創(chuàng)建數(shù)據(jù)庫表結構的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-10-10
  • Thinkphp 空操作、空控制器、命名空間(詳解)

    Thinkphp 空操作、空控制器、命名空間(詳解)

    下面小編就為大家?guī)硪黄猅hinkphp 空操作、空控制器、命名空間(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • PHP對稱加密函數(shù)實現(xiàn)數(shù)據(jù)的加密解密

    PHP對稱加密函數(shù)實現(xiàn)數(shù)據(jù)的加密解密

    本文詳細介紹了PHP一個簡單的對稱加密函數(shù)實現(xiàn)數(shù)據(jù)的加密解密,詳細的介紹了對稱加密和非對稱加密,有需要的可以了解一下。
    2016-10-10
  • PHPMYADMIN 簡明安裝教程 推薦

    PHPMYADMIN 簡明安裝教程 推薦

    簡單的說,phpmyadmin就是一種mysql的管理工具,安裝該工具后,即可以通過web形式直接管理mysql數(shù)據(jù),而不需要通過執(zhí)行系統(tǒng)命令來管理,非常適合對數(shù)據(jù)庫操作命令不熟悉的數(shù)據(jù)庫管理者,下面我就說下怎么安裝該工具
    2010-03-03
  • php記錄搜索引擎爬行記錄的實現(xiàn)代碼

    php記錄搜索引擎爬行記錄的實現(xiàn)代碼

    這篇文章主要介紹了php記錄搜索引擎爬行記錄的實現(xiàn)代碼,然后在文中給大家補充介紹了php獲取各搜索蜘蛛爬行記錄的代碼,需要的朋友可以參考下
    2018-03-03
  • TP5框架實現(xiàn)簽到功能的方法分析

    TP5框架實現(xiàn)簽到功能的方法分析

    這篇文章主要介紹了TP5框架實現(xiàn)簽到功能的方法,結合實例形式分析了TP5框架實現(xiàn)簽到功能相關數(shù)據(jù)表建立、以及數(shù)據(jù)的查詢、判斷、寫入等相關操作技巧,需要的朋友可以參考下
    2020-04-04

最新評論

三都| 临潭县| 翁牛特旗| 禹州市| 遂川县| 中江县| 久治县| 循化| 米泉市| 新闻| 大方县| 尉犁县| 陇西县| 邓州市| 肃宁县| 阳春市| 墨玉县| 木兰县| 永嘉县| 五家渠市| 道孚县| 尖扎县| 马鞍山市| 辽宁省| 金坛市| 福清市| 阜南县| 绥阳县| 潼南县| 中西区| 彰化市| 毕节市| 隆安县| 含山县| 额尔古纳市| 平湖市| 行唐县| 铁岭县| 元氏县| 广东省| 长兴县|