PHP Phalcon基于事件实现非入侵注解记录方法执行时间

最近做接口优化,经常需要在代码中入侵式的记日志去记录每个方法的执行时间,为了方便,研究了一下phalcon,实现了基于注解记录方法的执行时间的方法

重点知识

phalcon的事件通知

phalcon容器实例化类,会有两个钩子事件
di:beforeServiceResolve 实例化前消息
di:afterServiceResolve 实例化后消息

// 监听消息
// 获取容器工厂
$di = new FactoryDefault();

/** @var Manager $eventsManager */
$eventsManager = Di::getDefault()->get('eventsManager');
$eventsManager->attach('di:beforeServiceResolve', function(Event $event, FactoryDefault $di, array $factory){
  //执行一些代码
});

// 往工厂中注册事件
$di->setInternalEventsManager($eventManager);

通过Phalcon\Di\Service::setDefinition 改变容器创建对象的行为

在每一个service创建前,我们只需要通过setDefinition修改实际需要创建的对象以及创建过程,容器就会生产出我们所要的对象,definition的结构参考http://www.myleftstudio.com/reference/di.html

        $service->setDefinition([
            "className" => LogRuntimeProxy::class,
            "arguments" => [
                [
                    "type" => "instance",
                    "className" => $definition['className'],
                    "arguments" => $arguments
                ],
                ["type" => "parameter", "value" => $targetMethod],
                ["type" => "parameter", "value" => $logNames],
            ]
        ])

结合以上两点
我们只需要在di:beforeServiceResolve消息触发时,获取到即将被实例化的类,并且判断注解中是否有标记记录执行时间的注解,如果有的话,使用代理类代理该类,使得容器最终实例化的类为代理类即可

代码

  • 注册监听
use Phalcon\Di\FactoryDefault;
use Kernel\listeners\DiListener;

// 监听消息
// 获取容器工厂
$di = new FactoryDefault();

/** @var Manager $eventsManager */
$eventsManager = Di::getDefault()->get('eventsManager');
$eventsManager->attach('di', new DiListener());

// 往工厂中注册事件
$di->setInternalEventsManager($eventManager);

  • 事件处理器
<?php
/**
 * Created by PhpStorm.
 * User: chenyu
 * Date: 2021-11-18
 * Time: 11:54
 */

namespace Kernel\listeners;


use Kernel\components\annotations\LogRuntimeProxy;
use Phalcon\Annotations\Adapter\AdapterInterface;
use Phalcon\Di\FactoryDefault;
use Phalcon\Di\Injectable;
use Phalcon\Di\ServiceInterface;
use Phalcon\Events\Event;

/**
 * 容器在实例化对象前后触发事件
 * Class DiListener
 * @package Kernel\listeners
 */
class DiListener extends Injectable
{
    /**
     * 实例化前触发
     * @param Event $event 事件对象
     * @param FactoryDefault $di 容器工厂
     * @param array $factory 实例化对象的工厂配置["name" => 对象名称, "parameter" => 传入类构造器的参数]
     * @throws \Exception
     * @return bool
     */
    public function beforeServiceResolve(Event $event, FactoryDefault $di, array $factory)
    {
        // 获取服务器的类名
        if(!$di->has($factory['name'])){
            return true;
        }

        $service = $di->getService($factory['name']);
        $definition = $service->getDefinition();
        if (is_array($definition) && isset($definition['className'])) {
            /** @var AdapterInterface $annotations */
            $annotations = $di->get('annotations');

            // 获取类的注解
            $annotation = $annotations->get($definition['className']);

            // 检查类的注解是否有
            if ($annotation->getClassAnnotations()->has("logRuntime")) {

                $this->replaceDefinitionByProxy($service, $definition, $di);

                return true;
            }

            // 检查方法的注解
            $methodAnnotations = $annotation->getMethodsAnnotations();
            $targetMethod = [];
            $logNames = [];

            foreach ($methodAnnotations as $methodName => $annotation) {
                if ($annotation->has("logRuntime")) {

                    if($annotation->get("logRuntime")->hasArgument("logName")){
                        $logNames[$methodName] = $annotation->get("logRuntime")->getArgument("logName");
                    }

                    $targetMethod[] = $methodName;
                }
            }

            if (count($targetMethod) > 0) {
                $this->replaceDefinitionByProxy($service, $definition, $di, $targetMethod, $logNames);
            }

            return true;

        }
    }

    /**
     * 解析原方法的参数
     * @param $arguments
     * @param FactoryDefault $di
     * @return array
     * @throws \Exception
     */
    private function parseDefinitionArguments($arguments, FactoryDefault $di)
    {
        $parameters = [];
        foreach ($arguments as $argument) {
            switch ($argument['type']) {
                case "parameter":
                    $parameters[] = $argument["value"];
                    break;
                case "service" :
                    $parameters[] = $di->get($argument['name']);
                    break;
                case "instance" :
                    if (!class_exists($argument['className'])) {
                        throw new \Exception("class {$argument['className']} not found");
                    }
                    $parameters[] = new $argument['className'](...$argument["arguments"]);
                    break;
            }
        }

        return $parameters;
    }


    private function replaceDefinitionByProxy(ServiceInterface $service, array $definition, FactoryDefault $di, $targetMethod = [], $logNames = [])
    {
        $arguments = [];
        if (isset($definition['arguments'])) {
            $arguments = $this->parseDefinitionArguments($definition['arguments'], $di);
        }

        $definitions = [
            "className" => LogRuntimeProxy::class,
            "arguments" => [
                [
                    "type" => "instance",
                    "className" => $definition['className'],
                    "arguments" => $arguments
                ],
                ["type" => "parameter", "value" => $targetMethod],
                ["type" => "parameter", "value" => $logNames],
            ]
        ];

        if (isset($definition['calls'])) {
            $definitions['calls'] = $definition['calls'];
        }

        if (isset($definition['properties'])) {
            $definitions['properties'] = $definition['properties'];
        }

        $service->setDefinition($definitions);
    }

//    public function afterServiceResolve(...$parameter)
//    {
//        var_dump($parameter);
//    }
}
  • 代理类
<?php
/**
 * Created by PhpStorm.
 * User: chenyu
 * Date: 2021-11-18
 * Time: 15:20
 */

namespace Kernel\components\annotations;


use Kernel\components\Helper;
use Monolog\Logger;
use \Phalcon\Di;
use \Phalcon\Di\DiInterface;

/**
 * 事件日志代理类
 * Class LogRuntimeProxy
 * @package Kernel\components\annotations
 */
class LogRuntimeProxy
{
    /** @var object $target 目标对象 */
    private $target;
    /** @var array $targetMethod 目标方法 */
    private $targetMethod;

    /** @var array $logNames 方法的日志名(tag)只有作用在方法时生效,类时不生效 */
    private $logNames = [];

    /**
     * LogRuntimeProxy constructor.
     * @param object $target
     * @param array $targetMethod
     * @param array $logNames
     */
    public function __construct(object $target, array $targetMethod = [], array $logNames = [])
    {
        $this->target = $target;
        $this->targetMethod = $targetMethod;
        $this->logNames = $logNames;
    }


    /**
     * 代理方法
     * @param $name
     * @param $arguments
     * @return mixed
     * @throws \Exception
     */
    public function __call($name, $arguments)
    {
        if (!method_exists($this->target, $name)) {
            throw new \Exception("method not found");
        }

        // 如果没有指定目标方法或当前方法就是目标方法,则记录目标方法执行时间,否则直接执行目标方法返回
        if (empty($this->targetMethod) || in_array($name, $this->targetMethod)) {
            $class_name = get_class($this->target);
            $a = Helper::secMicrotime();

            $result = $this->target->$name(...$arguments);

            $b = Helper::secMicrotime();
            /** @var Logger $logger */
            $logger = Di::getDefault()->get("logger");
            if (isset($this->logNames[$name])) {
                $logger->withName($this->logNames[$name])->log("info", $class_name . "." . $name . ":" . ($b - $a));
            } else {
                $logger->log("info", $class_name . "." . $name . ":" . ($b - $a));
            }
        } else {
            $result = $this->target->$name(...$arguments);
        }

        return $result;
    }

    /**
     * set方法,为了支持通过properties注入依赖的场景
     * @param $name
     * @param $value
     * @throws \Exception
     */
    public function __set($name, $value)
    {
        if (!property_exists($this->target, $name)) {
            throw new \Exception("property not found");
        }

        $this->target->$name = $value;
    }
}
  • 注解使用
<?php
/**
 * Created by PhpStorm.
 * User: chenyu
 * Date: 2021-06-23
 * Time: 18:35
 */

namespace App\entity\Echat;


use App\entity\Entity;

/**
 * Class User
 * @package App\entity\Echat
 * 用在类上
 * @logRuntime
 */
class User extends Entity
{
    private $data = [];

    private $fillable = [
        "vip",
        "uid",
        "grade",
        "category",
        "name",
        "nickName",
        "gender",
        "age",
        "birthday",
        "maritalStatus",
        "phone",
        "qq",
        "wechat",
        "email",
        "nation",
        "province",
        "city",
        "address",
        "photo",
        "memo",
        "c1"
    ];

    public function __construct($attribute)
    {
        parent::__construct([]);
        foreach ($this->fillable as $field) {
            if (isset($attribute[$field])) {
                $this->data[$field] = $attribute[$field];
            }
        }
    }

    /**
     * 返回xml
     * @return string
     * 用在方法上
     * @logRuntime(logName="name")
     */
    public function toXml()
    {
        $xml = "";
        foreach ($this->data as $field => $datum) {
            if (in_array($field, $this->fillable)) {
                $xml .= "<{$field}><![CDATA[{$datum}]]></{$field}>";
            }
        }

        return empty($xml) ? $xml : "<xml>{$xml}</xml>";
    }

    public function toArray()
    {
        return $this->data;
    }

    public function offsetExists($offset)
    {
        return isset($this->data[$offset]);
    }

    public function offsetGet($offset)
    {
        return isset($this->data[$offset]) ? $this->data[$offset] : null;
    }

    public function offsetSet($offset, $value)
    {
        if (in_array($offset, $this->fillable)) {
            $this->data[$offset] = $value;
        }

        return $this;
    }

    public function offsetUnset($offset)
    {
        if (isset($this->data[$offset])) {
            unset($this->data[$offset]);
        }

        return $this;
    }

    public function __get($name)
    {
        return isset($this->data[$name]) ? $this->data[$name] : null;
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->fillable)) {
            $this->data[$name] = $value;
        }

        return $this;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容