refactor all base things

This commit is contained in:
crazywhalecc
2022-08-13 17:00:29 +08:00
committed by Jerry Ma
parent 1c801bb205
commit b2c95d96b1
54 changed files with 3009 additions and 383 deletions

View File

@@ -39,6 +39,17 @@ abstract class AnnotationBase implements IteratorAggregate
return $str;
}
/**
* 在 InstantPlugin 下调用,设置回调或匿名函数
*
* @param Closure|string $method
*/
public function withMethod($method): AnnotationBase
{
$this->method = $method;
return $this;
}
public function getIterator(): Traversable
{
return new ArrayIterator($this);

View File

@@ -4,10 +4,9 @@ declare(strict_types=1);
namespace ZM\Annotation;
use Generator;
use Throwable;
use ZM\Annotation\Middleware\Middleware;
use ZM\Exception\InterruptException;
use ZM\Middleware\MiddlewareHandler;
/**
* 注解调用器,原 EventDispatcher
@@ -39,17 +38,34 @@ class AnnotationHandler
/** @var mixed */
private $return_val;
/**
* 注解调用器构造函数
*
* @param string $annotation_class 注解类名
*/
public function __construct(string $annotation_class)
{
$this->annotation_class = $annotation_class;
logger()->debug('开始分发注解 {annotation}', ['annotation' => $annotation_class]);
}
/**
* 立刻中断注解调用器执行
*
* @param mixed $return_var 中断执行返回值传入null则代表无返回值
* @throws InterruptException
*/
public static function interrupt($return_var = null)
{
throw new InterruptException($return_var);
}
/**
* 设置执行前判断注解是否应该被执行的检查回调函数
*
* @param callable $rule 回调函数
* @return $this
*/
public function setRuleCallback(callable $rule): AnnotationHandler
{
logger()->debug('注解调用器设置事件ruleFunc: {annotation}', ['annotation' => $this->annotation_class]);
@@ -57,6 +73,12 @@ class AnnotationHandler
return $this;
}
/**
* 设置成功执行后有返回值时执行的返回值后续逻辑回调函数
*
* @param callable $return 回调函数
* @return $this
*/
public function setReturnCallback(callable $return): AnnotationHandler
{
logger()->debug('注解调用器设置事件returnFunc: {annotation}', ['annotation' => $this->annotation_class]);
@@ -65,119 +87,97 @@ class AnnotationHandler
}
/**
* @param mixed ...$params
* 调用注册了该注解的所有函数们
* 此处会遍历所有注册了当前注解的函数,并支持中间件插入
*
* @param mixed ...$params 传入的参数们
* @throws Throwable
*/
public function handleAll(...$params)
{
try {
// 遍历注册的注解
foreach ((AnnotationMap::$_list[$this->annotation_class] ?? []) as $v) {
// 调用单个注解
$this->handle($v, $this->rule_callback, ...$params);
// 执行完毕后检查状态如果状态是规则判断或中间件before不通过则重置状态后继续执行别的注解函数
if ($this->status == self::STATUS_BEFORE_FAILED || $this->status == self::STATUS_RULE_FAILED) {
$this->status = self::STATUS_NORMAL;
continue;
}
// 如果执行完毕,且设置了返回值后续逻辑的回调函数,那么就调用返回值回调的逻辑
if (is_callable($this->return_callback) && $this->status === self::STATUS_NORMAL) {
($this->return_callback)($this->return_val);
}
}
} catch (InterruptException $e) {
// InterruptException 用于中断,这里必须 catch并标记状态
$this->return_val = $e->return_var;
$this->status = self::STATUS_INTERRUPTED;
} catch (Throwable $e) {
// 其他类型的异常就顺势再抛出到外层,此层不做处理
$this->status = self::STATUS_EXCEPTION;
throw $e;
}
}
/**
* 调用单个注解
*
* @param mixed ...$params 传入的参数们
* @throws InterruptException
* @throws Throwable
*/
public function handle(AnnotationBase $v, ?callable $rule_callback = null, ...$params): bool
{
$target_class = resolve($v->class);
// 由于3.0有额外的插件模式支持,所以注解就不再提供独立的闭包函数调用支持了
// 提取要调用的目标类和方法名称
$target_class = new ($v->class)();
$target_method = $v->method;
// 先执行规则
if ($rule_callback !== null && !$rule_callback($this, $params)) {
// 先执行规则失败就返回false
if ($rule_callback !== null && !$rule_callback($v, $params)) {
$this->status = self::STATUS_RULE_FAILED;
return false;
}
// 检查中间件
$mid_obj = [];
$before_result = true;
foreach ($this->getRegisteredMiddlewares($target_class, $target_method) as $v) {
$mid_obj[] = $v[0]; // 投喂中间件
if ($v[1] !== '') { // 顺带执行before
if (function_exists('container')) {
$before_result = container()->call([$v[0], $v[1]], $params);
} else {
$before_result = call_user_func([$v[0], $v[1]], $params);
}
if ($before_result === false) {
break;
}
}
}
$mid_obj_cnt1 = count($mid_obj) - 1;
if ($before_result) { // before全部通过了
try {
// 执行注解绑定的方法
// TODO: 记得完善好容器后把这里的这个if else去掉
if (function_exists('container')) {
$this->return_val = container()->call([$target_class, $target_method], $params);
} else {
$this->return_val = call_user_func([$target_class, $target_method], $params);
}
} catch (Throwable $e) {
if ($e instanceof InterruptException) {
throw $e;
}
for ($i = $mid_obj_cnt1; $i >= 0; --$i) {
$obj = $mid_obj[$i];
foreach ($obj[3] as $name => $method) {
if ($e instanceof $name) {
$obj[0]->{$method}($e);
return false;
}
}
}
throw $e;
}
} else {
$this->status = self::STATUS_BEFORE_FAILED;
}
for ($i = $mid_obj_cnt1; $i >= 0; --$i) {
if ($mid_obj[$i][2] !== '') {
$mid_obj[$i][0]->{$mid_obj[$i][2]}($this->return_val);
$callback = [$target_class, $target_method];
try {
// 这块代码几乎等同于 middleware()->process() 中的内容,但由于注解调用器内含有一些特殊的特性(比如返回值回调),所以需要拆开来
$before_result = middleware()->processBefore($callback, $params);
if ($before_result) {
// before都通过了就执行本身通过依赖注入执行
// $this->return_val = container()->call($callback, $params);
$this->return_val = $callback(...$params);
} else {
// 没通过就标记是BEFORE_FAILED然后接着执行after
$this->status = self::STATUS_BEFORE_FAILED;
}
middleware()->processAfter($callback, $params);
} /* @noinspection PhpRedundantCatchClauseInspection */ catch (InterruptException $e) {
// 这里直接抛出这个异常的目的就是给上层handleAll()捕获
throw $e;
} catch (Throwable $e) {
// 其余的异常就交给中间件的异常捕获器过一遍,没捕获的则继续抛出
$this->status = self::STATUS_EXCEPTION;
MiddlewareHandler::getInstance()->processException($callback, $params, $e);
}
return true;
}
/**
* 获取注册过的中间件
*
* @param object|string $class 类对象
* @param string $method 方法名称
* 获取分发的状态
*/
private function getRegisteredMiddlewares($class, string $method): Generator
public function getStatus(): int
{
foreach (AnnotationMap::$_map[get_class($class)][$method] ?? [] as $annotation) {
if ($annotation instanceof Middleware) {
$name = $annotation->name;
$reg_mid = AnnotationMap::$_middleware_map[$name]['class'] ?? null;
if ($reg_mid === null) {
logger()->error('Not a valid middleware name: {name}', ['name' => $name]);
continue;
}
return $this->status;
}
$obj = new $reg_mid($annotation->params);
yield [
$obj,
AnnotationMap::$_middleware_map[$name]['before'] ?? '',
AnnotationMap::$_middleware_map[$name]['after'] ?? '',
AnnotationMap::$_middleware_map[$name]['exceptions'] ?? [],
];
}
}
return [];
/**
* 获取运行的返回值
*
* @return mixed
*/
public function getReturnVal()
{
return $this->return_val;
}
}

View File

@@ -10,7 +10,7 @@ namespace ZM\Annotation;
class AnnotationMap
{
/**
* 存取注解对象的列表
* 存取注解对象的列表key是注解类名value是该注解对应的数组
*
* @var array<string, array<AnnotationBase>>
* @internal
@@ -18,14 +18,22 @@ class AnnotationMap
public static $_list = [];
/**
* 存取注解对象的三维列表key1是注解所在的类名key2是注解所在的方法名value是该方法标注的注解们数组
*
* @var array<string, array<string, array<AnnotationBase>>>
* @internal
*/
public static $_map = [];
/**
* @var array
* @internal
* 将Parser解析后的注解注册到全局的 AnnotationMap
*
* @param AnnotationParser $parser 注解解析器
*/
public static $_middleware_map = [];
public static function loadAnnotationByParser(AnnotationParser $parser)
{
// 生成后加入到全局list中
self::$_list = array_merge(self::$_list, $parser->generateAnnotationList());
self::$_map = $parser->getAnnotationMap();
}
}

View File

@@ -10,64 +10,90 @@ use Koriym\Attributes\DualReader;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use Symfony\Component\Routing\RouteCollection;
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\Route;
use ZM\Annotation\Interfaces\ErgodicAnnotation;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Middleware\HandleAfter;
use ZM\Annotation\Middleware\HandleBefore;
use ZM\Annotation\Middleware\HandleException;
use ZM\Annotation\Middleware\Middleware;
use ZM\Annotation\Middleware\MiddlewareClass;
use ZM\Config\ZMConfig;
use ZM\Exception\ConfigException;
use ZM\Store\FileSystem;
use ZM\Store\InternalGlobals;
use ZM\Utils\HttpUtil;
/**
* 注解解析器
*/
class AnnotationParser
{
/**
* @var array 要解析的路径列表
*/
private $path_list = [];
/**
* @var float 用于计算解析时间用的
*/
private $start_time;
/**
* @var array 用于解析的注解解析树,格式见下方的注释
*/
private $annotation_tree = [];
/**
* @var array 用于生成"类-方法"对应"注解列表"的数组
*/
private $annotation_map = [];
private $middleware_map = [];
private $middlewares = [];
/** @var null|AnnotationReader|DualReader */
private $reader;
private $req_mapping = [];
/**
* @var array 特殊的注解解析器回调列表
*/
private $special_parsers = [];
/**
* AnnotationParser constructor.
*/
public function __construct()
public function __construct(bool $with_internal_parsers = true)
{
$this->start_time = microtime(true);
// $this->loadAnnotationClasses();
$this->req_mapping[0] = [
'id' => 0,
'pid' => -1,
'name' => '/',
];
if ($with_internal_parsers) {
$this->special_parsers = [
Middleware::class => [function (Middleware $middleware) { \middleware()->bindMiddleware([resolve($middleware->class), $middleware->method], $middleware->name, $middleware->params); }],
Route::class => [[$this, 'addRouteAnnotation']],
];
}
}
/**
* 设置自定义的注解解析方法
*
* @param string $class_name 注解类名
* @param callable $callback 回调函数
*/
public function addSpecialParser(string $class_name, callable $callback)
{
$this->special_parsers[$class_name][] = $callback;
}
/**
* 注册各个模块类的注解和模块level的排序
*
* @throws ReflectionException
* @throws ConfigException
*/
public function parseAll()
{
// 对每个设置的路径依次解析
foreach ($this->path_list as $path) {
logger()->debug('parsing annotation in ' . $path[0] . ':' . $path[1]);
// 首先获取路径下所有的类(通过 PSR-4 标准解析)
$all_class = FileSystem::getClassesPsr4($path[0], $path[1]);
// 读取配置文件中配置的忽略解析的注解名,防止误解析一些别的地方需要的注解,比如@mixin
$conf = ZMConfig::get('global.runtime.annotation_reader_ignore');
// 有两种方式,第一种是通过名称,第二种是通过命名空间
if (isset($conf['name']) && is_array($conf['name'])) {
foreach ($conf['name'] as $v) {
AnnotationReader::addGlobalIgnoredName($v);
@@ -78,14 +104,18 @@ class AnnotationParser
AnnotationReader::addGlobalIgnoredNamespace($v);
}
}
// 因为mixin常用且框架默认不需要解析则全局忽略
AnnotationReader::addGlobalIgnoredName('mixin');
$this->reader = new DualReader(new AnnotationReader(), new AttributeReader());
// 声明一个既可以解析注解又可以解析Attribute的双reader来读取注解和Attribute
$reader = new DualReader(new AnnotationReader(), new AttributeReader());
foreach ($all_class as $v) {
logger()->debug('正在检索 ' . $v);
// 通过反射实现注解读取
$reflection_class = new ReflectionClass($v);
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
$class_annotations = $this->reader->getClassAnnotations($reflection_class);
$class_annotations = $reader->getClassAnnotations($reflection_class);
// 这段为新加的:start
// 这里将每个类里面所有的类注解、方法注解通通加到一颗大树上,后期解析
/*
@@ -105,64 +135,85 @@ class AnnotationParser
}
*/
// 生成主树
$this->annotation_map[$v]['class_annotations'] = $class_annotations;
$this->annotation_map[$v]['methods'] = $methods;
// 保存对class的注解
$this->annotation_tree[$v]['class_annotations'] = $class_annotations;
// 保存类成员的方法的对应反射对象们
$this->annotation_tree[$v]['methods'] = $methods;
// 保存对每个方法获取到的注解们
foreach ($methods as $method) {
$this->annotation_map[$v]['methods_annotations'][$method->getName()] = $this->reader->getMethodAnnotations($method);
$this->annotation_tree[$v]['methods_annotations'][$method->getName()] = $reader->getMethodAnnotations($method);
}
foreach ($this->annotation_map[$v]['class_annotations'] as $vs) {
// 因为适用于类的注解有一些比较特殊,比如有向下注入的,有控制行为的,所以需要遍历一下下放到方法里
foreach ($this->annotation_tree[$v]['class_annotations'] as $vs) {
$vs->class = $v;
// 预处理1将适用于每一个函数的注解到类注解重新注解到每个函数下面
if (($vs instanceof ErgodicAnnotation) && ($vs instanceof AnnotationBase)) {
foreach (($this->annotation_map[$v]['methods'] ?? []) as $method) {
// 预处理0排除所有非继承于 AnnotationBase 的注解
if (!$vs instanceof AnnotationBase) {
logger()->notice(get_class($vs) . ' is not extended from ' . AnnotationBase::class);
continue;
}
// 预处理1如果类包含了@Closed注解则跳过这个类
if ($vs instanceof Closed) {
unset($this->annotation_tree[$v]);
continue 2;
}
// 预处理2将适用于每一个函数的注解到类注解重新注解到每个函数下面
if ($vs instanceof ErgodicAnnotation) {
foreach (($this->annotation_tree[$v]['methods'] ?? []) as $method) {
// 用 clone 的目的是生成个独立的对象,避免和 class 以及方法之间互相冲突
$copy = clone $vs;
$copy->method = $method->getName();
$this->annotation_map[$v]['methods_annotations'][$method->getName()][] = $copy;
$this->annotation_tree[$v]['methods_annotations'][$method->getName()][] = $copy;
}
}
// 预处理2处理 class 下面的注解
if ($vs instanceof Closed) {
unset($this->annotation_map[$v]);
continue 2;
}
if ($vs instanceof MiddlewareClass) {
// 注册中间件本身的类,标记到 middlewares 属性中
logger()->debug('正在注册中间件 ' . $reflection_class->getName());
$rs = $this->registerMiddleware($vs, $reflection_class);
$this->middlewares[$rs['name']] = $rs;
// 预处理3调用自定义解析器
foreach (($this->special_parsers[get_class($vs)] ?? []) as $parser) {
$result = $parser($vs);
if ($result === true) {
continue 2;
}
if ($result === false) {
continue 3;
}
}
}
$inserted = [];
// 预处理3处理每个函数上面的特殊注解就是需要操作一些东西的
foreach (($this->annotation_map[$v]['methods_annotations'] ?? []) as $method_name => $methods_annotations) {
foreach (($this->annotation_tree[$v]['methods_annotations'] ?? []) as $method_name => $methods_annotations) {
foreach ($methods_annotations as $method_anno) {
/* @var AnnotationBase $method_anno */
// 预处理3.0:排除所有非继承于 AnnotationBase 的注解
if (!$method_anno instanceof AnnotationBase) {
logger()->notice('Binding annotation ' . get_class($method_anno) . ' to ' . $v . '::' . $method_name . ' is not extended from ' . AnnotationBase::class);
continue;
}
// 预处理3.1:给所有注解对象绑定当前的类名和方法名
$method_anno->class = $v;
$method_anno->method = $method_name;
if (!($method_anno instanceof Middleware) && ($middlewares = ZMConfig::get('global.global_middleware_binding')[get_class($method_anno)] ?? []) !== []) {
if (!isset($inserted[$v][$method_name])) {
// 在这里在其他中间件前插入插入全局的中间件
foreach ($middlewares as $middleware) {
$mid_class = new Middleware($middleware);
$mid_class->class = $v;
$mid_class->method = $method_name;
$this->middleware_map[$v][$method_name][] = $mid_class;
}
$inserted[$v][$method_name] = true;
}
} elseif ($method_anno instanceof Route) {
$this->addRouteAnnotation($method_anno, $method_name, $v, $methods_annotations);
} elseif ($method_anno instanceof Middleware) {
$this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno;
} else {
AnnotationMap::$_map[$method_anno->class][$method_anno->method][] = $method_anno;
// 预处理3.2:如果包含了@Closed注解则跳过这个方法的注解解析
if ($method_anno instanceof Closed) {
unset($this->annotation_tree[$v]['methods_annotations'][$method_name]);
continue 2;
}
// 预处理3.3:调用自定义解析器
foreach (($this->special_parsers[get_class($method_anno)] ?? []) as $parser) {
$result = $parser($method_anno);
if ($result === true) {
continue 2;
}
if ($result === false) {
continue 3;
}
}
// 如果上方没有解析或返回了 true则添加到注解解析列表中
$this->annotation_map[$v][$method_name][] = $method_anno;
}
}
}
@@ -170,10 +221,13 @@ class AnnotationParser
logger()->debug('解析注解完毕!');
}
public function generateAnnotationEvents(): array
/**
* 生成排序后的注解列表
*/
public function generateAnnotationList(): array
{
$o = [];
foreach ($this->annotation_map as $obj) {
foreach ($this->annotation_tree as $obj) {
// 这里的ErgodicAnnotation是为了解决类上的注解可穿透到方法上的问题
foreach (($obj['class_annotations'] ?? []) as $class_annotation) {
if ($class_annotation instanceof ErgodicAnnotation) {
@@ -193,22 +247,9 @@ class AnnotationParser
return $o;
}
public function getMiddlewares(): array
{
return $this->middlewares;
}
public function getMiddlewareMap(): array
{
return $this->middleware_map;
}
public function getReqMapping(): array
{
return $this->req_mapping;
}
/**
* 添加解析的路径
*
* @param string $path 注册解析注解的路径
* @param string $indoor_name 起始命名空间的名称
*/
@@ -219,6 +260,8 @@ class AnnotationParser
}
/**
* 排序注解列表
*
* @param array $events 需要排序的
* @param string $class_name 排序的类名
* @param string $prefix 前缀
@@ -229,53 +272,37 @@ class AnnotationParser
if (is_a($class_name, Level::class, true)) {
$class_name .= $prefix;
usort($events[$class_name], function ($a, $b) {
$left = $a->level;
$right = $b->level;
$left = $a->getLevel();
$right = $b->getLevel();
return $left > $right ? -1 : ($left == $right ? 0 : 1);
});
}
}
public function getUsedTime()
/**
* 获取解析器调用的时间(秒)
*/
public function getUsedTime(): float
{
return microtime(true) - $this->start_time;
}
// private function below
private function registerMiddleware(MiddlewareClass $vs, ReflectionClass $reflection_class): array
/**
* 获取注解的注册map
*/
public function getAnnotationMap(): array
{
$result = [
'class' => '\\' . $reflection_class->getName(),
'name' => $vs->name,
];
foreach ($reflection_class->getMethods() as $vss) {
$method_annotations = $this->reader->getMethodAnnotations($vss);
foreach ($method_annotations as $vsss) {
if ($vsss instanceof HandleBefore) {
$result['before'] = $vss->getName();
}
if ($vsss instanceof HandleAfter) {
$result['after'] = $vss->getName();
}
if ($vsss instanceof HandleException) {
$result['exceptions'][$vsss->class_name] = $vss->getName();
}
}
}
return $result;
return $this->annotation_map;
}
private function addRouteAnnotation(Route $vss, $method, $class, $methods_annotations)
/**
* 添加注解路由
*/
private function addRouteAnnotation(Route $vss)
{
if (InternalGlobals::$routes === null) {
InternalGlobals::$routes = new RouteCollection();
}
// 拿到所属方法的类上面有没有控制器的注解
$prefix = '';
foreach ($methods_annotations as $annotation) {
foreach (($this->annotation_tree[$vss->class]['methods_annotations'][$vss->method] ?? []) as $annotation) {
if ($annotation instanceof Controller) {
$prefix = $annotation->prefix;
break;
@@ -284,9 +311,9 @@ class AnnotationParser
$tail = trim($vss->route, '/');
$route_name = $prefix . ($tail === '' ? '' : '/') . $tail;
logger()->debug('添加路由:' . $route_name);
$route = new \Symfony\Component\Routing\Route($route_name, ['_class' => $class, '_method' => $method]);
$route = new \Symfony\Component\Routing\Route($route_name, ['_class' => $vss->class, '_method' => $vss->method]);
$route->setMethods($vss->request_method);
InternalGlobals::$routes->add(md5($route_name), $route);
HttpUtil::getRouteCollection()->add(md5($route_name), $route);
}
}

View File

@@ -12,9 +12,9 @@ use Doctrine\Common\Annotations\Annotation\Target;
* Class Closed
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
* @Target("ALL")
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_ALL)]
class Closed extends AnnotationBase
{
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace ZM\Annotation\Framework;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class BindEvent
* 通过注解绑定 EventProvider 支持的事件
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("METHOD")
* @since 3.0.0
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)]
class BindEvent extends AnnotationBase implements Level
{
/**
* @var string
* @Required()
*/
public $event_class;
/** @var int */
public $level = 800;
/**
* @param string $event_class 绑定事件的类型
*/
public function __construct(string $event_class, int $level = 800)
{
$this->event_class = $event_class;
$this->level = $level;
}
public function getLevel(): int
{
return $this->level;
}
public function setLevel($level)
{
$this->level = $level;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace ZM\Annotation\Framework;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class Init
* @Annotation
* @NamedArgumentConstructor()
* @Target("METHOD")
* @since 3.0.0
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)]
class Init extends AnnotationBase
{
/** @var int */
public $worker = 0;
public function __construct(int $worker = 0)
{
$this->worker = $worker;
}
}

View File

@@ -14,8 +14,9 @@ use ZM\Annotation\AnnotationBase;
* @Annotation
* @NamedArgumentConstructor()
* @Target("METHOD")
* @since 3.0.0
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)]
class OnSetup extends AnnotationBase
class Setup extends AnnotationBase
{
}

View File

@@ -48,4 +48,9 @@ class Route extends AnnotationBase
$this->request_method = $request_method;
$this->params = $params;
}
public static function make($route, $name = '', $request_method = ['GET', 'POST'], $params = [])
{
return new static($route, $name, $request_method, $params);
}
}

View File

@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
namespace ZM\Annotation\OneBot;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
use ZM\Exception\InvalidArgumentException;
use ZM\Exception\ZMKnownException;
/**
* Class BotCommand
* 机器人指令注解
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("METHOD")
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)]
class BotCommand extends AnnotationBase implements Level
{
/** @var string */
public $name = '';
/** @var string */
public $match = '';
/** @var string */
public $pattern = '';
/** @var string */
public $regex = '';
/** @var string */
public $start_with = '';
/** @var string */
public $end_with = '';
/** @var string */
public $keyword = '';
/** @var string[] */
public $alias = [];
/** @var string */
public $message_type = '';
/** @var string */
public $user_id = '';
/** @var string */
public $group_id = '';
/** @var int */
public $level = 20;
/** @var array */
private $arguments = [];
public function __construct(
$name = '',
$match = '',
$pattern = '',
$regex = '',
$start_with = '',
$end_with = '',
$keyword = '',
$alias = [],
$message_type = '',
$user_id = '',
$group_id = '',
$level = 20
) {
$this->name = $name;
$this->match = $match;
$this->pattern = $pattern;
$this->regex = $regex;
$this->start_with = $start_with;
$this->end_with = $end_with;
$this->keyword = $keyword;
$this->alias = $alias;
$this->message_type = $message_type;
$this->user_id = $user_id;
$this->group_id = $group_id;
$this->level = $level;
}
public static function make(
$name = '',
$match = '',
$pattern = '',
$regex = '',
$start_with = '',
$end_with = '',
$keyword = '',
$alias = [],
$message_type = '',
$user_id = '',
$group_id = '',
$level = 20
): BotCommand {
return new static(...func_get_args());
}
/**
* @throws InvalidArgumentException
* @throws ZMKnownException
* @return $this
*/
public function withArgument(
string $name,
string $description = '',
string $type = 'string',
bool $required = false,
string $prompt = '',
string $default = '',
int $timeout = 60,
int $error_prompt_policy = 1
): BotCommand {
$this->arguments[] = new CommandArgument($name, $description, $type, $required, $prompt, $default, $timeout, $error_prompt_policy);
return $this;
}
public function getLevel(): int
{
return $this->level;
}
/**
* @param int $level
*/
public function setLevel($level)
{
$this->level = $level;
}
public function getArguments(): array
{
return $this->arguments;
}
}

View File

@@ -10,12 +10,14 @@ use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* 机器人相关事件注解
*
* @Annotation
* @Target("METHOD")
* @NamedArgumentConstructor
* @NamedArgumentConstructor()
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class OnOneBotEvent extends AnnotationBase
class BotEvent extends AnnotationBase
{
/** @var null|string */
public $type;
@@ -50,4 +52,15 @@ class OnOneBotEvent extends AnnotationBase
$this->self_id = $self_id;
$this->sub_type = $sub_type;
}
public static function make(
?string $type = null,
?string $detail_type = null,
?string $impl = null,
?string $platform = null,
?string $self_id = null,
?string $sub_type = null
): BotEvent {
return new static(...func_get_args());
}
}

View File

@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
namespace ZM\Annotation\OneBot;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Required;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\ErgodicAnnotation;
use ZM\Exception\InvalidArgumentException;
use ZM\Exception\ZMKnownException;
/**
* Class CommandArgument
* @Annotation
* @NamedArgumentConstructor()
* @Target("ALL")
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_ALL)]
class CommandArgument extends AnnotationBase implements ErgodicAnnotation
{
/**
* @var string
* @Required()
*/
public $name;
/**
* @var string
*/
public $description = '';
/**
* @var string
*/
public $type = 'string';
/**
* @var bool
*/
public $required = false;
/**
* @var string
*/
public $prompt = '';
/**
* @var string
*/
public $default = '';
/**
* @var int
*/
public $timeout = 60;
/**
* @var int
*/
public $error_prompt_policy = 1;
/**
* @param string $name 参数名称(可以是中文)
* @param string $description 参数描述(默认为空)
* @param bool $required 参数是否必需如果是必需为true默认为false
* @param string $prompt 当参数为必需时,返回给用户的提示输入的消息(默认为"请输入$name"
* @param string $default 当required为false时未匹配到参数将自动使用default值默认为空
* @param int $timeout prompt超时时间默认为60秒
* @throws InvalidArgumentException|ZMKnownException
*/
public function __construct(
string $name,
string $description = '',
string $type = 'string',
bool $required = false,
string $prompt = '',
string $default = '',
int $timeout = 60,
int $error_prompt_policy = 1
) {
$this->name = $name;
$this->description = $description;
$this->type = $this->fixTypeName($type);
$this->required = $required;
$this->prompt = $prompt;
$this->default = $default;
$this->timeout = $timeout;
$this->error_prompt_policy = $error_prompt_policy;
if ($this->type === 'bool') {
if ($this->default === '') {
$this->default = 'yes';
}
if (!in_array($this->default, array_merge(TRUE_LIST, FALSE_LIST))) {
throw new InvalidArgumentException('CommandArgument参数 ' . $name . ' 类型传入类型应为布尔型,检测到非法的默认值 ' . $this->default);
}
} elseif ($this->type === 'number') {
if ($this->default === '') {
$this->default = '0';
}
if (!is_numeric($this->default)) {
throw new InvalidArgumentException('CommandArgument参数 ' . $name . ' 类型传入类型应为数字型,检测到非法的默认值 ' . $this->default);
}
}
}
public function getTypeErrorPrompt(): string
{
return '参数类型错误,请重新输入!';
}
public function getErrorQuitPrompt(): string
{
return '参数类型错误,停止输入!';
}
/**
* @throws ZMKnownException
*/
protected function fixTypeName(string $type): string
{
$table = [
'str' => 'string',
'string' => 'string',
'strings' => 'string',
'byte' => 'string',
'num' => 'number',
'number' => 'number',
'int' => 'number',
'float' => 'number',
'double' => 'number',
'boolean' => 'bool',
'bool' => 'bool',
'true' => 'bool',
'any' => 'any',
'all' => 'any',
'*' => 'any',
];
if (array_key_exists($type, $table)) {
return $table[$type];
}
throw new ZMKnownException(zm_internal_errcode('E00077') . 'Invalid argument type: ' . $type . ', only support any, string, number and bool !');
}
}