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