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

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

作者: 自我De救赎 | 来源:发表于2021-11-18 18:46 被阅读0次

最近做接口优化,经常需要在代码中入侵式的记日志去记录每个方法的执行时间,为了方便,研究了一下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;
    }
}

相关文章

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/bhhjtrtx.html