本篇內(nèi)容主要講解“ThinkPHP5怎么引入Go AOP和PHP AOP編程”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“ThinkPHP5怎么引入Go AOP和PHP AOP編程”吧!
專業(yè)從事成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計,高端網(wǎng)站制作設(shè)計,小程序開發(fā),網(wǎng)站推廣的成都做網(wǎng)站的公司。優(yōu)秀技術(shù)團(tuán)隊竭力真誠服務(wù),采用html5+CSS3前端渲染技術(shù),響應(yīng)式網(wǎng)站設(shè)計,讓網(wǎng)站在手機(jī)、平板、PC、微信下都能呈現(xiàn)。建站過程建立專項小組,與您實時在線互動,隨時提供解決方案,暢聊想法和感受。
目前開發(fā)的WEB軟件里有這一個功能,PHP訪問API操作數(shù)據(jù)倉庫,剛開始數(shù)據(jù)倉庫小,沒發(fā)現(xiàn)問題,隨著數(shù)據(jù)越來越多,調(diào)用API時常超時(60s)。于是決定采用異步請求,改為60s能返回數(shù)據(jù)則返回,不能則返回一個異步ID,然后輪詢是否完成統(tǒng)計任務(wù)。由于項目緊,人手不足,必須以最小的代價解決當(dāng)前問題。
重新分析需求,并改進(jìn)代碼
采用AOP方式改動程序
從新做需求分析,以及詳細(xì)設(shè)計,并改動代碼,需要產(chǎn)品,架構(gòu),前端,后端的支持。會驚動的人過多,在資源緊張的情況下是不推薦的。
采用AOP方式,不改動原有代碼邏輯,只需要后端就能完成大部分任務(wù)了。后端用AOP切入請求API的方法,通過監(jiān)聽API返回的結(jié)果來控制是否讓其繼續(xù)運行原有的邏輯(API在60s返回了數(shù)據(jù)),或者是進(jìn)入離線任務(wù)功能(API報告統(tǒng)計任務(wù)不能在60s內(nèi)完成)。
之前用過AOP-PHP
拓展,上手很簡單,不過后來在某一個大項目中引入該拓展后,直接爆了out of memory
,然后就研究其源碼發(fā)現(xiàn),它改變了語法樹,并Hook了每個被調(diào)用的方法,也就是每個方法被調(diào)用是都會去詢問AOP-PHP
,這個方法有沒有切面方法。所以效率損失是比較大的。而且這個項目距離現(xiàn)在已經(jīng)有8年沒更新了。所以不推薦該解決方案。
Debian,php-fpm-7.0,ThinkPHP-5.10。
作為一門zui好的語言,PHP是不自帶AOP的。那就得安裝AOP-PHP拓展,當(dāng)我打開pecl要下載時,傻眼了,全是bate版,沒有顯示說明支持php7。但我還是抱著僥幸心理,找到了git,發(fā)現(xiàn)4-5年沒更新了,要不要等一波更新,哦,作者在issue里說了有時間就開始兼容php7。
好吧,狠話不多說,下一個方案:Go!AOP.看了下git,作者是個穿白體恤,喜歡山峰的大帥哥,基本每個issue都會很熱心回復(fù)。
composer require goaop/framework
ThinkPHP5 對composer兼容挺不錯的哦,(到后面,我真想揍ThinkPHP5作者)這就裝好了,怎么用啊,git上的提示了簡單用法。我也就照著寫了個去切入controller。
aspectAction(); } public function aspectAction() { return 'hello'; } }
定義aspect
aspectAction(*))") */ public function beforeMethodExecution(MethodInvocation $invocation) { $obj = $invocation->getThis(); echo 'Calling Before Interceptor for method: ', is_object($obj) ? get_class($obj) : $obj, $invocation->getMethod()->isStatic() ? '::' : '->', $invocation->getMethod()->getName(), '()', ' with arguments: ', json_encode($invocation->getArguments()), "
\n"; } }
啟用aspect
registerAspect(new MonitorAspect()); } }
go-aop 核心服務(wù)配置
init([ 'debug' => true, 'appDir' => __DIR__ . './../../../', 'cacheDir' => __DIR__ . './../../../runtime/aop_cache', 'includePaths' => [ __DIR__ . './../../tests/controller', __DIR__ . './../../../thinkphp/library/think/model' ], 'excludePaths' => [ __DIR__ . './../../aspect', ] ]); return $params; } }
配置模塊init鉤子,讓其啟動 go-aop
[], // 應(yīng)用開始 'app_begin' => [], // 模塊初始化 'module_init' => [ 'app\\tests\\behavior\\Bootstrap' ], // 操作開始執(zhí)行 'action_begin' => [], // 視圖內(nèi)容過濾 'view_filter' => [], // 日志寫入 'log_write' => [], // 應(yīng)用結(jié)束 'app_end' => [], ];
好了,訪問 http://127.0.0.1/tests/test1/... 顯示:
hello
這不是預(yù)期的效果,在aspect定義了,訪問該方法前,會輸出方法的更多信息信息。
像如下內(nèi)容才是預(yù)期
Calling Before Interceptor for method: app\tests\controller\Test1->aspectAction() with arguments: []
上他官方Doc看看,是一些更高級的用法。沒有講go-aop的運行機(jī)制。
上git上也沒看到類似issue,額,發(fā)現(xiàn)作者經(jīng)常在issue里回復(fù):試一試demo。也許我該試試demo。
我采用的是LNMP技術(shù)棧。
假設(shè)這里有臺Ubuntu你已經(jīng)配置好了LNMP環(huán)境
下載代碼
配置nginx
# file: /usr/share/etc/nginx/conf.d/go-aop-test.conf server { listen 8008; # listen 443 ssl; server_name 0.0.0.0; root "/usr/share/nginx/html/app/vendor/lisachenko/go-aop-php/demos"; index index.html index.htm index.php; charset utf-8; access_log /var/log/nginx/go-aop-access.log; error_log /var/log/nginx/go-aop-error.log notice; sendfile off; client_max_body_size 100m; location ~ \.php(.*)$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param PATH_INFO $fastcgi_path_info; # fastcgi_param SCRIPT_FILENAME /var/www/html/app/vendor/lisachenko/go-aop-php/demos$fastcgi_script_name; #docker的配置 fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/api/vendor/lisachenko/go-aop-php/demos$fastcgi_script_name; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_split_path_info ((?U).+\.php)(/?.+)$; } }
接下來要調(diào)整下代碼
訪問 http://127.0.0.1:8008 試試,(估計大家都遇到了這個)
這個報錯信息提示找不到這個類。來到報錯的文件里。這文件使用了use找不到類,就是autoload出問題了,看到 vendor/lisachenko/go-aop-php/demos/autoload.php
這個文件。
add('Demo', __DIR__); }
可以看到這個代碼第一行沒找到vendor下的autoload。我們做如下調(diào)整
add('Demo', __DIR__); }
再試試,demo運行起來了。
嘗試了下,運行成功
通過以上的輸出,可以得出demo里是對方法運行前成功捕獲。為什么在thinkphp的controller里運行就不成功呢。我決定采用斷點進(jìn)行調(diào)試。
通過斷點我發(fā)現(xiàn)了這個文件
original->findFile($class)) { $isInternal = false; foreach ($this->internalNamespaces as $ns) { if (strpos($class, $ns) === 0) { $isInternal = true; break; } } include ($isInternal ? $file : FilterInjectorTransformer::rewrite($file)); } }
這是一個autoload,每個類的載入都會經(jīng)過它,并且會對其判斷是否為內(nèi)部類,不是的都會進(jìn)入后續(xù)的操作。通過斷點進(jìn)入 FilterInjectorTransformer,發(fā)現(xiàn)會對load的文件進(jìn)行語法解析,并根據(jù)注冊的annotation對相關(guān)的類生成proxy類。說道這,大家就明白了go-aop是如何做到切入你的程序了吧,生成的proxy類,可以在你配置的cache-dir(我配置的是./runtime/aop_cache/
)里看到。
同時./runtime/aop_cache/ 文件夾下也生成了很多東西,通過查看aop_cache文件內(nèi)產(chǎn)生了與Test1文件名相同的文件,打開文件,發(fā)現(xiàn)它代理了原有的Test1控制器。這一系列信息,可以得出,Go!AOP 通過"劫持" composer autoload 讓每個類都進(jìn)過它,根據(jù)aspect的定義來決定是否為其創(chuàng)建一個代理類,并植入advice。
額,ThinkPHP5是把composer autoload里的東西copy出來,放到自己autoload里,然后就沒composer啥事了。然后go-aop一直等不到composer autoload下發(fā)的命令,自然就不能起作用了,so,下一步
在ThinkPHP5里,默認(rèn)有且只會注冊一個TP5內(nèi)部的 Loader,并不會把include請求下發(fā)給composer的autoload。所以,為其讓go-aop起作用,那么必須讓讓include class的請求經(jīng)過 AopComposerLoad.
我們看看這個文件
loadClass($class); return class_exists($class, false); }); $loader[0] = new AopComposerLoader($loader[0]); } spl_autoload_unregister($loaderToUnregister); } unset($loader); foreach ($loaders as $loader) { spl_autoload_register($loader); } }
這個文件里有個類型檢測,檢測autoload callback是否為Classloader類型,然而ThinkPHP5不是,通過斷點你會發(fā)現(xiàn)ThinkPHP5是一個字符串?dāng)?shù)組,so,這里也就無法把go-aop注冊到class loader的callback當(dāng)中了。
這里就要提一下PHP autoload機(jī)制了,這是現(xiàn)代PHP非常重要的一個功能,它讓我們在用到一個類時,通過名字能自動加載文件。我們通過定義一定的類名規(guī)則與文件結(jié)構(gòu)目錄,再加上能實現(xiàn)以上規(guī)則的函數(shù)就能實現(xiàn)自動加載了。在通過 spl_autoload_register 函數(shù)的第三個參數(shù) prepend
設(shè)置為true,就能讓其排在在TP5的loader前面,先一步被調(diào)用。
依照如上原理,就可以做如下改進(jìn)
這個是為go-aop包裝的新autoload,本質(zhì)上是在原來的ThinkPHP5的loader上加了一個殼而已。
getMethod('findFile'); $method->setAccessible(true); $file = $method->invoke($obj, $class); return $file; } }
loadClass($class); return class_exists($class, false); }); $aopLoader = new AopComposerLoader($thinkLoader); spl_autoload_register([$aopLoader, 'loadClass'], false, true); return $params;
在這里我們做了一個autload 并直接把它插入到了最前面(如果項目內(nèi)還有其他autloader,請注意他們的先后順序)。
現(xiàn)在我們再訪問一下http://127.0.0.1/tests/test1/test1你就能看到來自 aspect
輸出的信息了。
最后我們做個總結(jié):
PHP7
目前沒有拓展實現(xiàn)的 AOP
。
ThinkPHP5
有著自己的 Autoloader
。
Go!AOP
的AOP實現(xiàn)依賴Class Autoload
的 callback
,通過替換原文件
指向Proxy類
實現(xiàn)。
ThinkPHP5
整合 Go!AOP
需要調(diào)整 autoload
。
到此,相信大家對“ThinkPHP5怎么引入Go AOP和PHP AOP編程”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!