返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >ThinkPHP5怎么引入Go AOP和PHP AOP编程
  • 855
分享到

ThinkPHP5怎么引入Go AOP和PHP AOP编程

2023-06-20 16:06:49 855人浏览 安东尼
摘要

本篇内容主要讲解“ThinkPHP5怎么引入Go aop和php AOP编程”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“ThinkPHP5怎么引入Go AOP和PHP AOP编程”吧!项目背景

本篇内容主要讲解“ThinkPHP5怎么引入Go aopphp 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都会很热心回复。

composer require goaop/framework

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

<?PHPnamespace app\tests\controller;use think\Controller;class Test1 extends Controller{    public function test1()    {        echo $this->aspectAction();    }        public function aspectAction()    {        return 'hello';    }}

定义aspect

<?PHPnamespace 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{        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

<?PHP// file: ./application/tests/service/ApplicationAspecTKErnel.phpnamespace app\tests\service;use Go\Core\AspectKernel;use Go\Core\AspectContainer;use app\tests\aspect\MonitorAspect;class ApplicationAspectKernel extends AspectKernel{        protected function configureAop(AspectContainer $container)    {        $container->reGISterAspect(new MonitorAspect());    }}

go-aop 核心服务配置

<?PHP// file: ./application/tests/behavior/Bootstrap.phpnamespace 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

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

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

# file: /usr/share/etc/nginx/conf.d/go-aop-test.confserver {    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 这个文件。

<?PHP···if (file_exists(__DIR__ . '/../vendor/autoload.php')) {        $loader = include __DIR__ . '/../vendor/autoload.php';    $loader->add('Demo', __DIR__);}

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

<?PHP$re = __DIR__ . '/../../../vendor/autoload.php';if (file_exists(__DIR__ . '/../../../autoload.php')) {        $loader = include __DIR__ . '/../../../autoload.php';    $loader->add('Demo', __DIR__);}

再试试,demo运行起来了。

ThinkPHP5怎么引入Go AOP和PHP AOP编程

尝试了下,运行成功

ThinkPHP5怎么引入Go AOP和PHP AOP编程

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

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

<?PHP// file: ./vendor/lisachenko/go-aop-php/src/Instrument/ClassLoading/AopComposerLoader.phppublic 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.
我们看看这个文件

<?PHP// ./vendor/lisachenko/go-aop-php/src/Instrument/ClassLoading/AopComposerLoader.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) 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上加了一个壳而已。

<?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;        public function loadClass($class)    {        return Loader::autoload($class);    }        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;    }}
<?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

到此,相信大家对“ThinkPHP5怎么引入Go AOP和PHP AOP编程”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

--结束END--

本文标题: ThinkPHP5怎么引入Go AOP和PHP AOP编程

本文链接: https://lsjlt.com/news/298323.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • ThinkPHP5怎么引入Go AOP和PHP AOP编程
    本篇内容主要讲解“ThinkPHP5怎么引入Go AOP和PHP AOP编程”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“ThinkPHP5怎么引入Go AOP和PHP AOP编程”吧!项目背景...
    99+
    2023-06-20
  • Spring中的AOP编程怎么应用
    本文小编为大家详细介绍“Spring中的AOP编程怎么应用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Spring中的AOP编程怎么应用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。AOP 概述AOP 为 A...
    99+
    2023-07-05
  • Quarkus中的依赖注入DI和面向切面aop编程
    目录前言JSR365:Java2.0的上下文和依赖注规范Bean声明和依赖注入Bean的生命周期条件化初始Bean面向切面编程aopBean列表接口结语前言 做java开发的肯定清楚...
    99+
    2024-04-02
  • Java面向切面编程AOP怎么实现
    这篇文章主要介绍“Java面向切面编程AOP怎么实现”,在日常操作中,相信很多人在Java面向切面编程AOP怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java面向切面编程AOP怎么实现”的疑惑有所...
    99+
    2023-06-04
  • Python面向切面编程AOP及装饰器怎么使用
    本篇内容主要讲解“Python面向切面编程AOP及装饰器怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python面向切面编程AOP及装饰器怎么使用”吧!什么是 AOPAOP,就是面向切...
    99+
    2023-06-30
  • php中怎么声明和引入命名空间
    这篇文章给大家介绍php中怎么声明和引入命名空间,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、命名空间1.1 介绍在一个大的项目中,可能会遇到同名的类、函数、常量,为了区分这些元素,我们可以将这些元素分别存放到不同...
    99+
    2023-06-20
  • 高效编程:PHP 索引的技巧和诀窍是什么?
    PHP 是一种广泛使用的编程语言,尤其在 Web 开发领域中得到了广泛应用。在 PHP 中,索引是一种非常重要的数据结构,它可以让我们快速地访问和操作数组、字符串等数据类型。在本文中,我们将探讨 PHP 索引的技巧和诀窍,以帮助您编写更高...
    99+
    2023-09-23
    索引 学习笔记 编程算法
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作