PHP - 给旧项目新增行为日志记录的方案思路(AOP)


在开发过程中,我们常常需要对代码的操作行为进行记录,在一般的情况下我们都可以通过自带的代码异常处理进行错误记录;但是开发一个后台管理系统的情况下我们也需要对“用户”的行为进行监控和记录。如果一开始编写记录Log的规范可能后续开发就无需再额外处理了,但现在已经生成了大量的代码并无法顾及到全部方法内添加方法,所以想通过一些偷懒的方法去完成这个功能。

该功能暂时没有应用到真实的业务场景上,请自行评估风险


应用到的环境说明

  • WordPress 5.5 & PHP7.4
  • 已经生成大量代码, 大部分是静态方法的调用
  • WordPress的应用上没有统一出入口(虽然可以通过一些Filter去统一)

设计思路

因为历史遗留的问题,我们旧的代码已经越来越大了,单个方法甚至已经操作了2000行,追踪到每个方法内去添加记录Log的函数十分消耗时间且枯燥,但好在我们开发的时候每个操作方法都会定义在一个类内,这样我们可以通过魔术方法 __call() 以及 __callStatic() 调用方法。

但是__call() 以及 __callStatic()只能应用在未定义的方法上,总不能在每个调用的方法或者类内的方法手动修改一遍吧,这不仅影响代码的可读性并且影响代码补全等功能,所以看起来可能这个方案不太行。

但是,按照这个思路去想,如果能从调用操作方法前已经通过代码修改原有的方法名(需要用到runkit7扩展),让全部调用原来的方法的地方全部走到该两个魔术方法上,再调用修改后的方法名,这样的执行方法的前后都可以拿到了。根据这个思路先编写测试代码。

<?php
class myClass(){
    //用于存在需要监听的原方法名
    static $proxy_methods = [];

    /*
    ** 假设需要记录日志的方法事件
    */
    public static function run(){
        echo __METHOD__
    }

    /*
    ** 给类内的方法进行注册改名
    */
    public static function _register_log(){
        $class   = new \ReflectionClass(get_class());
        $methods = $class->GetMethods();
        foreach($methods as $method){
            $method_name = $method->name;
            //这里会获取到魔术方法 应该做判断跳过魔术方法和该方法本身
            if(in_array($method_name, ['__call', '__callStatic', __METHOD__])){
                continue;
            }
            if(runkit7_method_rename($class, $method_name, $method_name.'_proxy')){
                self::$proxy_methods[] = $method->name
            }
        }
    }

    /*
    ** 让方法统一走进魔法函数
    */
    public static function __callStatic($method, $args){
        if(in_array($method, self::$proxy_methods)){
            //这里调用统一日志记录方法
            $call_method = $method.'_proxy';
            return static::$call_method($args);
        }
        //这里可以抛出method not found
    }
}

根据上面的思路,将注册方法_register_log()和编写魔术方法__callStatic()单独写成一个Trait赋予到需要用到到类内上就不用重复编写。

但是就算编写了以上的方法,可能需要在另外一个文件上,调用方法前对该类统一调用方法_register_log()将其类内方法进行修改后进行调用才能实现我们想要的效果。解决方案可以是在类定义的文件顶部加上这行进行修改。

因为WordPress是不支持自动加载类的,在执行代码的时候已经把需要用的代码通过include加载进来了,那么可以通过get_declared_classes()获取到此为止已定义的类,然后依次进行取出需要注册类调用注册方法。不过该方法不适用于自动加载


一些改善的地方

  • 根据上面所说的 将通用的方法整理到Trait方便代码复用
  • 可以配合反射类内的方法getTrait()配合使用拿到该类是否需要调用注册方法
  • 如果使用get_declared_classes()去获取类并注册的时候需要考虑到过滤类,可以通过上面的getTrait()配合使用,也可以通过编写规范注解解析处理,做成注解的可以使扩展性更强。
  • 一个类内可能不是每个方法都需要记录日志,所以也可以通过上面这条,在需要记录的方法上编写规范注解,并且可以在注解描述方法功能等。
  • 性能问题 这样的操作理论可行,但性能消耗上不确定是否会出现问题,还未测试