首页 > 作文

ThinkPHP5 框架引入 Go AOP,PHP AOP编程项目详解

更新时间:2023-04-08 19:02:36 阅读: 评论:0

本文实例讲述了thinkphp5 框架引入 go aop,php aop编程。分享给大家供大家参考,具体如下:

项目背景

目前开发的web软件里有这一个功能,php访问api操作数据仓库,刚开始数据仓库小,没发现问题,随着数据越来越多,调用api时常超时(60s)。于是决定采用异步请求,改为60s能返回数据则返回,不能则返回一个异步id,然后轮询是否完成统计任务。由于项目紧,人手不足,必须以最小的代价解决当前问题。

方案选择

重新分析需求,并改进代码采用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都会很热心回复。

compor require goaop/framework

thinkphp5 对compor兼容挺不错的哦,(到后面,我真想揍thinkphp5作者)这就装好了,怎么用啊,git上的提示了简单用法。我也就照着写了个去切入controller。

<?phpnamespace app\tests\controller;u think\controller;class test1 extends controller{ public function test1() {  echo $this->aspectaction(); }  public function aspectaction() {  return 'hello'; }}

定义aspect

<?phpnamespace app\tests\aspect;u go\aop\aspect;u go\aop\intercept\fieldaccess;u go\aop\intercept\methodinvocation;u go\lang\annotation\after;u go\lang\annotation\before;u go\lang\annotation\around;u go\lang\annotation\pointcut;u 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->getm茱莉亚的眼睛ethod()->isstatic() ? '::' : '->',    $invocation->getmethod()->getname(),    '()',    ' with arguments: ',    json_encode($invocation->getarguments()),    "<br>\n"; }}

启用aspect

<?php// file: ./application/tests/rvice/applicationaspectkernel.phpnamespace app\tests\rvice;u go\core\aspectkerne档案在自己手里l;u go\core\aspectcontainer;u app\tests\aspect\monitoraspect;/** * application aspect kernel * * class applicationaspectkernel * @package app\tests\rvice */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 核心服务配置

<?php// file: ./application/tests/behavior/bootstrap.phpnamespace app\tests\behavior;u think\exception;u compor\autoload\classloader;u go\instrument\transformer\filterinjectortransformer;u go\instrument\classloading\aopcomporloader;u doctrine\common\annotations\annotationregistry;u app\tests\rvice\applicationaspectkernel;u 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

<?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 显示:

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技术栈。

假设这里有台ubuntu你已经配置好了lnmp环境下载代码配置nginx
# file: /usr/share/etc/nginx/conf.d/go-aop-test.confrver { listen 8008;# listen 443 ssl; rver_name 0.0.0.0; root "/usr/share/nginx/html/app/vendor/lisachenko/go-aop-php/demos"; index index.html index.htm index.php; chart utf-8; access_log /var/log/nginx/go-aop-access.log; error_log /var/log/nginx/go-aop-error.log notice; ndfile 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)(/?.+)$; }}

接下来要调整下代码

访问 http://127.0.0.1:8008 试试,(估计大家都遇到了这个)这个报错信息提示找不到这个类。来到报错的文件里。这文件使用了u找不到类,就是autoload出问题了,看到 vendor/lisachenko/go-aop-php/demos/autoload.php 这个文件。
<?php···if (file_exists(__dir__ . '/../vendor/autoload.php')) { /** @var compor\autoload\classloader $loader */ $loader = include __dir__ . '/../vendor/autoload.php'; $loader->add('demo', __dir__);}

可以看到这个代码第一行没找到vendor下的autoload。我们做如下调整

<?php$re = __dir__ . '/../../../vendor/autoload.php';if (file_exists(__dir__ . '/../../../autoload.php')) { /** @var compor\autoload\classloader $loader */ $loader = include __dir__ . '/../../../autoload.php'; $loader->add('demo', __dir__);}

再试试,demo运行起来了。

尝试了下,运行成功

通过以上的输出,可以得出demo里是对方法运行前成功捕获。为什么在thinkphp的controller里运行就不成功呢。我决定采用断点进行调试。

通过断点我发现了这个文件

<?php// file: ./vendor/lisachenko/go-aop-php/src/instrument/classloading/aopcomporloader.phppublic function loadclass($class){ if ($file = $this->original->findfile($class)) {  $isinternal = fal;  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 通过”劫持” compor autoload 让每个类都进过它,根据aspect的定义来决定是否为其创建一个代理类,并植入advice。
额,thinkphp5是把compor autoload里的东西copy出来,放到自己autoload里,然后就没compor啥事了。然后go-aop一直等不到compor autoload下发的命令,自然就不能起作用了,so,下一步

改进thinkphp5

在thinkphp5里,默认有且只会注册一个tp5内部的 loader,并不会把include请求下发给compor的autoload。所以,为其让go-aop起作用,那么必须让让include class的请求经过 aopcomporload.
我们看看这个文件

<?php// ./vendor/lisachenko/go-aop-php/src/instrument/classloading/aopcomporloader.php:57public 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) u ($originalloader) {    $originalloader->loadclass($class);    return class_exists($class, fal);   });   $loader[0] = new aopcomporloader($loader[0]);  }  spl_autoload_unregister($loadertounregister); } unt($loader); foreach ($loaders as $loader) {  spl_autoload_register($loader); }}

这个文件里有个类型检测,检测autoload callback是否为classloader类型,然而thinkphp5不是,通过断点你会发现thinkphp5是一个字符串数组,so,这里也就无法把go-aop注册到class loader的callback当中了。

这里就要提一下php autoload机制了,这是现代php非常重要的一个功能,它让我们在用到一个类时,通过名字能自动加载文件。我们通过定义一定的类名规则与文件结构目录,再加上能实现以上规则的函数就能实现自动加载了。在通过 函数的第三个参数 prepend 设置为true,就能让其排在在tp5的loader前面,先一步被调用。

依照如上原理,就可以做如下改进
这个是为go-aop包装的新autoload,本质上是在原来的thinkphp5的loader上加了一个壳而已。

<?php// file: ./application/tests namespace app\tests;require_once __dir__ . './../../vendor/compor/classloader.php';u think\loader;u \compor\autoload\classloader;u go\instrument\transformer\filterinjectortransformer;u go\instrument\classloading\aopcomporloader;u 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 = fal;  foreach ($all学习效率owednamespace as $ns) {   if (strpos($class, $ns) === 0) {    $isallowed = true;    break;   }  }  // 不允许被aop的类,则不进入aopcompor  if(!$isallowed)   return fal;    $obj = new loader;  $obrver = new \reflectionclass(loader::class);  $method = $obrver->getmethod('findfile');  $method->taccessible(true);  $file = $method->invoke($obj, $class);  return $file; }}
<?php// file: ./application/tests/behavior/bootstrap.php 在刚刚我们新添加的文件当中// 这个方法 \app\tests\behavior\bootstrap::moduleinit 的后面追加如下内容// 组成aopcomporautoloader$originalloader = $thinkloader = new thinkphploaderwrapper();annotationregistry::registerloader(function ($class) u ($originalloader) { $originalloader->loadclass($class); return class_exists($class, fal);});$aoploader = new aopcomporloader($thinkloader);spl_autoload_register([$aoploader, 龙之谷哪个职业好玩'loadclass'], fal, true);return $params;

在这里我们做了一个autload 并直接把它插入到了最前面(如果项目内还有其他autloader,请注意他们的先后顺序)。

最后

现在我们再访问一下 http://127.0.0.1/tests/test1/test1 你就能看到来自 aspect 输出的信息了。

最后我们做个总结:

php7 目前没有拓展实现的 aopthinkphp5 有着自己的 autoloadergo!aop 的aop实现依赖class autoloadcallback,通过替换原文件指向proxy类实现。thinkphp5 整合 go!aop 需要调整 autoload

本文发布于:2023-04-08 19:02:32,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/13a85941a976620912652e1bf05bf6e5.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:ThinkPHP5 框架引入 Go AOP,PHP AOP编程项目详解.doc

本文 PDF 下载地址:ThinkPHP5 框架引入 Go AOP,PHP AOP编程项目详解.pdf

标签:文件   方法   断点   代码
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图