美文网首页
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