一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - PHP教程 - ThinkPHP5 框架引入 Go AOP,PHP AOP編程項目詳解

ThinkPHP5 框架引入 Go AOP,PHP AOP編程項目詳解

2020-06-24 15:03小白要生發 PHP教程

這篇文章主要介紹了ThinkPHP5 框架引入 Go AOP,PHP AOP編程,結合具體項目項目分析了ThinkPHP5 引入 Go AOP,PHP AOP編程相關概念、原理、操作技巧與注意事項,需要的朋友可以參考下

本文實例講述了ThinkPHP5 框架引入 Go AOP,PHP AOP編程。分享給大家供大家參考,具體如下:

項目背景

 

目前開發的WEB軟件里有這一個功能,PHP訪問API操作數據倉庫,剛開始數據倉庫小,沒發現問題,隨著數據越來越多,調用API時常超時(60s)。于是決定采用異步請求,改為60s能返回數據則返回,不能則返回一個異步ID,然后輪詢是否完成統計任務。由于項目緊,人手不足,必須以最小的代價解決當前問題。

方案選擇

 

  1. 重新分析需求,并改進代碼
  2. 采用AOP方式改動程序

從新做需求分析,以及詳細設計,并改動代碼,需要產品,架構,前端,后端的支持。會驚動的人過多,在資源緊張的情況下是不推薦的。
采用AOP方式,不改動原有代碼邏輯,只需要后端就能完成大部分任務了。后端用AOP切入請求API的方法,通過監聽API返回的結果來控制是否讓其繼續運行原有的邏輯(API在60s返回了數據),或者是進入離線任務功能(API報告統計任務不能在60s內完成)。

之前用過AOP-PHP拓展,上手很簡單,不過后來在某一個大項目中引入該拓展后,直接爆了out of memory,然后就研究其源碼發現,它改變了語法樹,并Hook了每個被調用的方法,也就是每個方法被調用是都會去詢問AOP-PHP,這個方法有沒有切面方法。所以效率損失是比較大的。而且這個項目距離現在已經有8年沒更新了。所以不推薦該解決方案。

實際環境

 

Debian,php-fpm-7.0,ThinkPHP-5.10。

引入AOP

 

作為一門zui好的語言,PHP是不自帶AOP的。那就得安裝AOP-PHP拓展,當我打開pecl要下載時,傻眼了,全是bate版,沒有顯示說明支持php7。但我還是抱著僥幸心理,找到了git,發現4-5年沒更新了,要不要等一波更新,哦,作者在issue里說了有時間就開始兼容php7。
好吧,狠話不多說,下一個方案:Go!AOP.看了下git,作者是個穿白體恤,喜歡山峰的大帥哥,基本每個issue都會很熱心回復。

?
1
composer require goaop/framework

ThinkPHP5 對composer兼容挺不錯的哦,(到后面,我真想揍ThinkPHP5作者)這就裝好了,怎么用啊,git上的提示了簡單用法。我也就照著寫了個去切入controller。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?PHP
namespace app\tests\controller;
 
use think\Controller;
 
class Test1 extends Controller
{
 public function test1()
 {
  echo $this->aspectAction();
 }
 
 public function aspectAction()
 {
  return 'hello';
 }
}

定義aspect

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?PHP
namespace app\tests\aspect;
 
use Go\Aop\Aspect;
use Go\Aop\Intercept\FieldAccess;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\After;
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
use Go\Lang\Annotation\Pointcut;
 
use app\tests\controller\Test1;
 
class MonitorAspect implements Aspect
{
 
 /**
  * Method that will be called before real method
  *
  * @param MethodInvocation $invocation Invocation
  * @Before("execution(public|protected app\tests\controller\Test1->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()),
    "<br>\n";
 }
}

啟用aspect

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?PHP
// file: ./application/tests/service/ApplicationAspectKernel.php
 
namespace app\tests\service;
 
use Go\Core\AspectKernel;
use Go\Core\AspectContainer;
 
use app\tests\aspect\MonitorAspect;
 
/**
 * Application Aspect Kernel
 *
 * Class ApplicationAspectKernel
 * @package app\tests\service
 */
class ApplicationAspectKernel extends AspectKernel
{
 
 /**
  * Configure an AspectContainer with advisors, aspects and pointcuts
  *
  * @param AspectContainer $container
  *
  * @return void
  */
 protected function configureAop(AspectContainer $container)
 {
  $container->registerAspect(new MonitorAspect());
 }
}

go-aop 核心服務配置

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?PHP
// file: ./application/tests/behavior/Bootstrap.php
namespace app\tests\behavior;
 
use think\Exception;
use Composer\Autoload\ClassLoader;
use Go\Instrument\Transformer\FilterInjectorTransformer;
use Go\Instrument\ClassLoading\AopComposerLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;
 
use app\tests\service\ApplicationAspectKernel;
use app\tests\ThinkPhpLoaderWrapper;
 
class Bootstrap
{
 public function moduleInit(&$params)
 {
  $applicationAspectKernel = ApplicationAspectKernel::getInstance();
  $applicationAspectKernel->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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?PHP
// file: ./application/tests/tags.php
// 由于是thinkphp5.10 沒有容器,所有需要在module下的tags.php文件里配置調用他
 
return [
 // 應用初始化
 'app_init'  => [],
 // 應用開始
 'app_begin' => [],
 // 模塊初始化
 'module_init' => [
  'app\\tests\\behavior\\Bootstrap'
 ],
 // 操作開始執行
 'action_begin' => [],
 // 視圖內容過濾
 'view_filter' => [],
 // 日志寫入
 'log_write' => [],
 // 應用結束
 'app_end'  => [],
];

兼容測試

 

好了,訪問 http://127.0.0.1/tests/test1/test1 顯示:

?
1
hello

這不是預期的效果,在aspect定義了,訪問該方法前,會輸出方法的更多信息信息。
像如下內容才是預期

Calling Before Interceptor for method: app\tests\controller\Test1->aspectAction() with arguments: []

上他官方Doc看看,是一些更高級的用法。沒有講go-aop的運行機制。
上git上也沒看到類似issue,額,發現作者經常在issue里回復:試一試demo。也許我該試試demo。

Run Demos

 

我采用的是LNMP技術棧。

  1. 假設這里有臺Ubuntu你已經配置好了LNMP環境
  2. 下載代碼
  3. 配置nginx
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 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)(/?.+)$;
 }
}

接下來要調整下代碼

  1. 訪問 http://127.0.0.1:8008 試試,(估計大家都遇到了這個)

ThinkPHP5 框架引入 Go AOP,PHP AOP編程項目詳解

  1. 這個報錯信息提示找不到這個類。來到報錯的文件里。這文件使用了use找不到類,就是autoload出問題了,看到 vendor/lisachenko/go-aop-php/demos/autoload.php 這個文件。
?
1
2
3
4
5
6
7
<?PHP
···
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
 /** @var Composer\Autoload\ClassLoader $loader */
 $loader = include __DIR__ . '/../vendor/autoload.php';
 $loader->add('Demo', __DIR__);
}

可以看到這個代碼第一行沒找到vendor下的autoload。我們做如下調整

?
1
2
3
4
5
6
7
<?PHP
$re = __DIR__ . '/../../../vendor/autoload.php';
if (file_exists(__DIR__ . '/../../../autoload.php')) {
 /** @var Composer\Autoload\ClassLoader $loader */
 $loader = include __DIR__ . '/../../../autoload.php';
 $loader->add('Demo', __DIR__);
}

再試試,demo運行起來了。

ThinkPHP5 框架引入 Go AOP,PHP AOP編程項目詳解

嘗試了下,運行成功

ThinkPHP5 框架引入 Go AOP,PHP AOP編程項目詳解

通過以上的輸出,可以得出demo里是對方法運行前成功捕獲。為什么在thinkphp的controller里運行就不成功呢。我決定采用斷點進行調試。

通過斷點我發現了這個文件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?PHP
// file: ./vendor/lisachenko/go-aop-php/src/Instrument/ClassLoading/AopComposerLoader.php
 
public function loadClass($class)
{
 if ($file = $this->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,每個類的載入都會經過它,并且會對其判斷是否為內部類,不是的都會進入后續的操作。通過斷點進入 FilterInjectorTransformer,發現會對load的文件進行語法解析,并根據注冊的annotation對相關的類生成proxy類。說道這,大家就明白了go-aop是如何做到切入你的程序了吧,生成的proxy類,可以在你配置的cache-dir(我配置的是./runtime/aop_cache/)里看到。

同時./runtime/aop_cache/ 文件夾下也生成了很多東西,通過查看aop_cache文件內產生了與Test1文件名相同的文件,打開文件,發現它代理了原有的Test1控制器。這一系列信息,可以得出,Go!AOP 通過"劫持" composer autoload 讓每個類都進過它,根據aspect的定義來決定是否為其創建一個代理類,并植入advice。
額,ThinkPHP5是把composer autoload里的東西copy出來,放到自己autoload里,然后就沒composer啥事了。然后go-aop一直等不到composer autoload下發的命令,自然就不能起作用了,so,下一步

改進ThinkPHP5

 

在ThinkPHP5里,默認有且只會注冊一個TP5內部的 Loader,并不會把include請求下發給composer的autoload。所以,為其讓go-aop起作用,那么必須讓讓include class的請求經過 AopComposerLoad.
我們看看這個文件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?PHP
// ./vendor/lisachenko/go-aop-php/src/Instrument/ClassLoading/AopComposerLoader.php:57
 
public static function init()
{
 $loaders = spl_autoload_functions();
 
 foreach ($loaders as &$loader) {
  $loaderToUnregister = $loader;
  if (is_array($loader) && ($loader[0] instanceof ClassLoader)) {
   $originalLoader = $loader[0];
 
   // Configure library loader for doctrine annotation loader
   AnnotationRegistry::registerLoader(function ($class) use ($originalLoader) {
    $originalLoader->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不是,通過斷點你會發現ThinkPHP5是一個字符串數組,so,這里也就無法把go-aop注冊到class loader的callback當中了。

這里就要提一下PHP autoload機制了,這是現代PHP非常重要的一個功能,它讓我們在用到一個類時,通過名字能自動加載文件。我們通過定義一定的類名規則與文件結構目錄,再加上能實現以上規則的函數就能實現自動加載了。在通過 spl_autoload_register 函數的第三個參數 prepend 設置為true,就能讓其排在在TP5的loader前面,先一步被調用。

依照如上原理,就可以做如下改進
這個是為go-aop包裝的新autoload,本質上是在原來的ThinkPHP5的loader上加了一個殼而已。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?PHP
// file: ./application/tests
 
namespace app\tests;
 
require_once __DIR__ . './../../vendor/composer/ClassLoader.php';
 
use think\Loader;
use \Composer\Autoload\ClassLoader;
use Go\Instrument\Transformer\FilterInjectorTransformer;
use Go\Instrument\ClassLoading\AopComposerLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;
 
 
class ThinkPhpLoaderWrapper extends ClassLoader
{
 static protected $thinkLoader = Loader::class;
 
 /**
  * Autoload a class by it's name
  */
 public function loadClass($class)
 {
  return Loader::autoload($class);
 }
 
 /**
  * {@inheritDoc}
  */
 public function findFile($class)
 {
  $allowedNamespace = [
   'app\tests\controller'
  ];
  $isAllowed = false;
  foreach ($allowedNamespace as $ns) {
   if (strpos($class, $ns) === 0) {
    $isAllowed = true;
    break;
   }
  }
  // 不允許被AOP的類,則不進入AopComposer
  if(!$isAllowed)
   return false;
  
  $obj = new Loader;
  $observer = new \ReflectionClass(Loader::class);
 
  $method = $observer->getMethod('findFile');
  $method->setAccessible(true);
  $file = $method->invoke($obj, $class);
  return $file;
 }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?PHP
// file: ./application/tests/behavior/Bootstrap.php 在剛剛我們新添加的文件當中
// 這個方法 \app\tests\behavior\Bootstrap::moduleInit 的后面追加如下內容
 
// 組成AOPComposerAutoLoader
$originalLoader = $thinkLoader = new ThinkPhpLoaderWrapper();
AnnotationRegistry::registerLoader(function ($class) use ($originalLoader) {
 $originalLoader->loadClass($class);
 
 return class_exists($class, false);
});
$aopLoader = new AopComposerLoader($thinkLoader);
spl_autoload_register([$aopLoader, 'loadClass'], false, true);
 
return $params;

在這里我們做了一個autload 并直接把它插入到了最前面(如果項目內還有其他autloader,請注意他們的先后順序)。

最后

 

現在我們再訪問一下 http://127.0.0.1/tests/test1/test1 你就能看到來自 aspect 輸出的信息了。

最后我們做個總結:

  1. PHP7 目前沒有拓展實現的 AOP。
  2. ThinkPHP5 有著自己的 Autoloader。
  3. Go!AOP 的AOP實現依賴Class Autoloadcallback,通過替換原文件指向Proxy類實現。
  4. ThinkPHP5 整合 Go!AOP 需要調整 autoload。

希望本文所述對大家基于ThinkPHP框架的PHP程序設計有所幫助。

原文鏈接:https://segmentfault.com/a/1190000012656671

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 福利片免费一区二区三区 | 欧美黄站 | 国产精品福利久久2020 | 三年片韩国在线观看 | 亚洲AV无码专区国产乱码网站 | 国产成人精品午夜免费 | 精品久久久久久国产91 | 热门小说同人h改编h | 国产欧美日韩图片一区二区 | 亚洲精品国产综合久久一线 | 国产成人一区二区三区视频免费蜜 | 精品一区二区三区 不卡高清 | 精品麻豆 | 国产成人高清亚洲一区91 | 痴mu动漫成年动漫在线观看 | 精品国产麻豆免费人成网站 | 甜性涩爱 | 特级淫片欧美高清视频蜜桃 | 色综合久久天天综合 | 亚洲国产情侣一区二区三区 | 婷婷久久热99在线精品 | 国产成人欧美视频在线 | 秋霞一级成人欧美理论 | 好女孩韩剧免费观看 | 毛毛片在线 | 欧美日韩一区二区三区久久 | 99精品热| 草草在线免费视频 | 91po国产在线高清福利 | 国产乱叫456在线 | 隔壁老王国产精品福利 | 四虎影库紧急大通知 | 天天干夜夜拍 | cosplay 极品videos| 亚洲AV 日韩 国产 有码 | а天堂中文最新版在线官网视频 | 亚洲麻豆精品果冻传媒 | 麻豆在线观看 | 欧美福利二区 | 午夜免费啪视频观看视频 | 欧美18-19|