mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-25 01:25:34 +08:00
add pipeline feature for middleware
This commit is contained in:
@@ -6,7 +6,6 @@ namespace ZM\Annotation;
|
|||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use ZM\Exception\InterruptException;
|
use ZM\Exception\InterruptException;
|
||||||
use ZM\Middleware\MiddlewareHandler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注解调用器,原 EventDispatcher
|
* 注解调用器,原 EventDispatcher
|
||||||
@@ -124,41 +123,30 @@ class AnnotationHandler
|
|||||||
/**
|
/**
|
||||||
* 调用单个注解
|
* 调用单个注解
|
||||||
*
|
*
|
||||||
* @param mixed ...$params 传入的参数们
|
|
||||||
* @throws InterruptException
|
* @throws InterruptException
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function handle(AnnotationBase $v, ?callable $rule_callback = null, ...$params): bool
|
public function handle(AnnotationBase $v, ?callable $rule_callback = null): bool
|
||||||
{
|
{
|
||||||
// 由于3.0有额外的插件模式支持,所以注解就不再提供独立的闭包函数调用支持了
|
// 由于3.0有额外的插件模式支持,所以注解就不再提供独立的闭包函数调用支持了
|
||||||
// 提取要调用的目标类和方法名称
|
// 提取要调用的目标类和方法名称
|
||||||
$target_class = new ($v->class)();
|
$target_class = new ($v->class)();
|
||||||
$target_method = $v->method;
|
$target_method = $v->method;
|
||||||
// 先执行规则,失败就返回false
|
// 先执行规则,失败就返回false
|
||||||
if ($rule_callback !== null && !$rule_callback($v, $params)) {
|
if ($rule_callback !== null && !$rule_callback($v)) {
|
||||||
$this->status = self::STATUS_RULE_FAILED;
|
$this->status = self::STATUS_RULE_FAILED;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$callback = [$target_class, $target_method];
|
$callback = [$target_class, $target_method];
|
||||||
try {
|
try {
|
||||||
// 这块代码几乎等同于 middleware()->process() 中的内容,但由于注解调用器内含有一些特殊的特性(比如返回值回调),所以需要拆开来
|
$this->return_val = middleware()->process($callback);
|
||||||
$before_result = middleware()->processBefore($callback, $params);
|
} catch (InterruptException $e) {
|
||||||
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()捕获
|
// 这里直接抛出这个异常的目的就是给上层handleAll()捕获
|
||||||
throw $e;
|
throw $e;
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
// 其余的异常就交给中间件的异常捕获器过一遍,没捕获的则继续抛出
|
// 其余的异常就交给中间件的异常捕获器过一遍,没捕获的则继续抛出
|
||||||
$this->status = self::STATUS_EXCEPTION;
|
$this->status = self::STATUS_EXCEPTION;
|
||||||
MiddlewareHandler::getInstance()->processException($callback, $params, $e);
|
throw $e;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ZM\Command\Server;
|
namespace ZM\Command\Server;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use OneBot\Driver\Process\ProcessManager;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use ZM\Exception\ConfigException;
|
|
||||||
use ZM\Exception\InitException;
|
use ZM\Exception\InitException;
|
||||||
use ZM\Exception\ZMKnownException;
|
use ZM\Exception\ZMKnownException;
|
||||||
use ZM\Framework;
|
use ZM\Framework;
|
||||||
@@ -45,7 +46,8 @@ class ServerStartCommand extends ServerCommand
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws ZMKnownException
|
* @throws ZMKnownException
|
||||||
* @throws ConfigException|InitException
|
* @throws InitException
|
||||||
|
* @throws Exception
|
||||||
* @noinspection PhpComposerExtensionStubsInspection
|
* @noinspection PhpComposerExtensionStubsInspection
|
||||||
*/
|
*/
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
@@ -57,7 +59,8 @@ class ServerStartCommand extends ServerCommand
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
if (\OneBot\Driver\Process\ProcessManager::isSupportedMultiProcess()) {
|
// 如果是支持多进程模式的,那么就检查框架进程的状态
|
||||||
|
if (ProcessManager::isSupportedMultiProcess()) {
|
||||||
$state = ProcessStateManager::getProcessState(ZM_PROCESS_MASTER);
|
$state = ProcessStateManager::getProcessState(ZM_PROCESS_MASTER);
|
||||||
if (!$input->getOption('no-state-check')) {
|
if (!$input->getOption('no-state-check')) {
|
||||||
if (is_array($state) && posix_getsid($state['pid'] ?? -1) !== false) {
|
if (is_array($state) && posix_getsid($state['pid'] ?? -1) !== false) {
|
||||||
@@ -67,6 +70,7 @@ class ServerStartCommand extends ServerCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 框架启动的入口
|
||||||
(new Framework($input->getOptions()))->init()->start();
|
(new Framework($input->getOptions()))->init()->start();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,54 +73,85 @@ class MiddlewareHandler
|
|||||||
$this->reg_map[$stack_id][] = [$name, $params];
|
$this->reg_map[$stack_id][] = [$name, $params];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPipeClosure(callable $callback, $stack_id)
|
||||||
|
{
|
||||||
|
unset($this->stack[$stack_id]);
|
||||||
|
/** @noinspection PhpUnnecessaryLocalVariableInspection */
|
||||||
|
$pipe_func = function (array $mid_list, ...$args) use ($callback, $stack_id, &$pipe_func) {
|
||||||
|
$return = true;
|
||||||
|
try {
|
||||||
|
while (($item = array_shift($mid_list)) !== null) {
|
||||||
|
$this->stack[$stack_id][] = $item;
|
||||||
|
// 如果是 pipeline 形式的中间件,则使用闭包回去
|
||||||
|
if (class_exists($item[0]) && is_a($item[0], PipelineInterface::class, true)) {
|
||||||
|
return resolve($item[0])->handle(function (...$args) use ($mid_list, &$pipe_func) {
|
||||||
|
return $pipe_func($mid_list, ...$args);
|
||||||
|
}, ...$args);
|
||||||
|
} elseif (isset($this->middlewares[$item[0]]['before'])) {
|
||||||
|
$return = container()->call($this->middlewares[$item[0]]['before'], $args);
|
||||||
|
// before 没执行完,直接跳出,不执行本体
|
||||||
|
if ($return === false) {
|
||||||
|
array_pop($this->callable_stack);
|
||||||
|
$mid_list = [];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($return !== false) {
|
||||||
|
$result = container()->call($callback, $args);
|
||||||
|
}
|
||||||
|
while (isset($this->stack[$stack_id]) && ($item = array_pop($this->stack[$stack_id])) !== null) {
|
||||||
|
// 如果是 pipeline 形式的中间件,则使用闭包回去
|
||||||
|
if (class_exists($item[0]) && is_a($item[0], PipelineInterface::class, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isset($this->middlewares[$item[0]]['after'])) {
|
||||||
|
$after_result = container()->call($this->middlewares[$item[0]]['after'], $args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
while (isset($this->stack[$stack_id]) && ($item = array_pop($this->stack[$stack_id])) !== null) {
|
||||||
|
// 如果是 pipeline 形式的中间件,则使用闭包回去
|
||||||
|
if (class_exists($item[0]) && is_a($item[0], PipelineInterface::class, true)) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (($this->middlewares[$item[0]]['exception'] ?? []) as $k => $v) {
|
||||||
|
if (is_a($e, $k)) {
|
||||||
|
$exception_result = $v($e);
|
||||||
|
unset($this->stack[$stack_id]);
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isset($exception_result)) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result ?? $after_result ?? $exception_result ?? null;
|
||||||
|
};
|
||||||
|
return $pipe_func;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function process(callable $callback, array $args)
|
public function process(callable $callback)
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
$before_result = MiddlewareHandler::getInstance()->processBefore($callback, $args);
|
|
||||||
if ($before_result) {
|
|
||||||
$result = container()->call($callback, $args);
|
|
||||||
}
|
|
||||||
MiddlewareHandler::getInstance()->processAfter($callback, $args);
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
MiddlewareHandler::getInstance()->processException($callback, $args, $e);
|
|
||||||
}
|
|
||||||
return $result ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调用中间件的前
|
|
||||||
*
|
|
||||||
* @param callable $callback 必须是数组形式的动态调用
|
|
||||||
* @param array $args 参数列表
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function processBefore(callable $callback, array $args): bool
|
|
||||||
{
|
|
||||||
// 压栈ID
|
|
||||||
$stack_id = $this->getStackId($callback);
|
$stack_id = $this->getStackId($callback);
|
||||||
// 清除之前的
|
|
||||||
unset($this->stack[$stack_id]);
|
unset($this->stack[$stack_id]);
|
||||||
|
|
||||||
$this->callable_stack[] = $callback;
|
$this->callable_stack[] = $callback;
|
||||||
|
|
||||||
// 遍历执行before并压栈,并在遇到返回false后停止
|
// 遍历执行before并压栈,并在遇到返回false后停止
|
||||||
try {
|
try {
|
||||||
foreach (($this->reg_map[$stack_id] ?? []) as $item) {
|
$mid_list = ($this->reg_map[$stack_id] ?? []);
|
||||||
$this->stack[$stack_id][] = $item;
|
$final_result = ($this->getPipeClosure($callback, $stack_id))($mid_list);
|
||||||
if (isset($this->middlewares[$item[0]]['before'])) {
|
|
||||||
$return = container()->call($this->middlewares[$item[0]]['before'], $args);
|
|
||||||
if ($return === false) {
|
|
||||||
array_pop($this->callable_stack);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
array_pop($this->callable_stack);
|
array_pop($this->callable_stack);
|
||||||
}
|
}
|
||||||
return true;
|
return $final_result ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,60 +164,11 @@ class MiddlewareHandler
|
|||||||
return end($this->callable_stack);
|
return end($this->callable_stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: 调用中间件的后
|
|
||||||
*
|
|
||||||
* @param callable $callback 必须是数组形式的动态调用
|
|
||||||
* @param array $args 参数列表
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function processAfter(callable $callback, array $args)
|
|
||||||
{
|
|
||||||
// 压栈ID
|
|
||||||
$stack_id = $this->getStackId($callback);
|
|
||||||
// 从栈内倒序取出已经执行过的中间件,并执行after
|
|
||||||
$this->callable_stack[] = $callback;
|
|
||||||
try {
|
|
||||||
while (isset($this->stack[$stack_id]) && ($item = array_pop($this->stack[$stack_id])) !== null) {
|
|
||||||
if (isset($this->middlewares[$item[0]]['after'])) {
|
|
||||||
container()->call($this->middlewares[$item[0]]['after'], $args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
array_pop($this->callable_stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: 调用中间件的异常捕获处理
|
|
||||||
*
|
|
||||||
* @param callable $callback 必须是数组形式的动态调用
|
|
||||||
* @param array $args 参数列表
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
* @throws Throwable
|
|
||||||
*/
|
|
||||||
public function processException(callable $callback, array $args, Throwable $throwable)
|
|
||||||
{
|
|
||||||
// 压栈ID
|
|
||||||
$stack_id = $this->getStackId($callback);
|
|
||||||
// 从栈内倒序取出已经执行过的中间件,并执行after
|
|
||||||
while (isset($this->stack[$stack_id]) && ($item = array_pop($this->stack[$stack_id])) !== null) {
|
|
||||||
foreach ($this->middlewares[$item[0]]['exception'] as $k => $v) {
|
|
||||||
if (is_a($throwable, $k)) {
|
|
||||||
$v($throwable, ...$args);
|
|
||||||
unset($this->stack[$stack_id]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw $throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callable $callback 可执行的方法
|
* @param callable $callback 可执行的方法
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
private function getStackId(callable $callback): string
|
public function getStackId(callable $callback): string
|
||||||
{
|
{
|
||||||
if ($callback instanceof Closure) {
|
if ($callback instanceof Closure) {
|
||||||
// 闭包情况下,直接根据闭包的ID号来找stack
|
// 闭包情况下,直接根据闭包的ID号来找stack
|
||||||
|
|||||||
60
src/ZM/Middleware/Pipeline.php
Normal file
60
src/ZM/Middleware/Pipeline.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ZM\Middleware;
|
||||||
|
|
||||||
|
use ZM\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipeline Inspired by Laravel Framework
|
||||||
|
*/
|
||||||
|
class Pipeline
|
||||||
|
{
|
||||||
|
/** @var mixed */
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $middlewares;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向管道发送数据
|
||||||
|
*
|
||||||
|
* @param mixed $value 数据
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function send($value): Pipeline
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定要过的中间件列表
|
||||||
|
*
|
||||||
|
* @param array $middlewares 要过的中间件(或者叫兼容管道的中间件也行)列表
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function through(array $middlewares): Pipeline
|
||||||
|
{
|
||||||
|
$this->middlewares = $middlewares;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接下来要调用的内容
|
||||||
|
*
|
||||||
|
* @param callable $callback 然后调用一个什么东西
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @return null|mixed 返回调用结果或null
|
||||||
|
*/
|
||||||
|
public function then(callable $callback)
|
||||||
|
{
|
||||||
|
$stack_id = middleware()->getStackId($callback);
|
||||||
|
|
||||||
|
// 遍历执行before并压栈,并在遇到返回false后停止
|
||||||
|
$final_result = (middleware()->getPipeClosure($callback, $stack_id))($this->middlewares, $this->value);
|
||||||
|
|
||||||
|
return $final_result ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/ZM/Middleware/PipelineInterface.php
Normal file
10
src/ZM/Middleware/PipelineInterface.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ZM\Middleware;
|
||||||
|
|
||||||
|
interface PipelineInterface
|
||||||
|
{
|
||||||
|
public function handle(callable $callback, ...$params);
|
||||||
|
}
|
||||||
@@ -4,25 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ZM\Middleware;
|
namespace ZM\Middleware;
|
||||||
|
|
||||||
class TimerMiddleware implements MiddlewareInterface
|
class TimerMiddleware implements MiddlewareInterface, PipelineInterface
|
||||||
{
|
{
|
||||||
/** @var float */
|
public function handle(callable $callback, ...$params)
|
||||||
private $starttime = 0;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
middleware()->registerBefore(static::class, [$this, 'onBefore']);
|
$starttime = microtime(true);
|
||||||
middleware()->registerAfter(static::class, [$this, 'onAfter']);
|
$result = $callback(...$params);
|
||||||
}
|
logger()->info('Pipeline using ' . round((microtime(true) - $starttime) * 1000, 4) . ' ms');
|
||||||
|
return $result;
|
||||||
public function onBefore(): bool
|
|
||||||
{
|
|
||||||
$this->starttime = microtime(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onAfter()
|
|
||||||
{
|
|
||||||
logger()->info('Using ' . round((microtime(true) - $this->starttime) * 1000, 4) . ' ms');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user