This commit is contained in:
2026-06-28 20:20:21 +08:00
parent fec0715c40
commit be7c5da071
8 changed files with 338 additions and 444 deletions
+1 -5
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Kiri\Router\Annotate;
use Kiri\Di\Interface\InjectMethodInterface;
use Kiri\Router\Defer\DeferRegistry;
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Defer implements InjectMethodInterface
@@ -18,11 +19,6 @@ class Defer implements InjectMethodInterface
}
/**
* @param string $class
* @param string $method
* @return void
*/
public function dispatch(string $class, string $method): void
{
DeferRegistry::add($class, $method, $this);
-263
View File
@@ -1,263 +0,0 @@
<?php
declare(strict_types=1);
namespace Kiri\Router\Annotate;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Swoole\Coroutine;
class DeferRegistry
{
/**
* @var array<string, Defer[]> "ClassName::method" => Defer[]
*/
private static array $registry = [];
/**
* @param string $class
* @param string $method
* @param Defer $defer
* @return void
*/
public static function add(string $class, string $method, Defer $defer): void
{
$key = self::key($class, $method);
self::$registry[$key][] = $defer;
}
/**
* @param string $class
* @param string $method
* @return Defer[]
*/
public static function get(string $class, string $method): array
{
return self::$registry[self::key($class, $method)] ?? [];
}
/**
* @param string $class
* @return bool
*/
public static function hasAny(string $class): bool
{
$prefix = $class . '::';
foreach (array_keys(self::$registry) as $key) {
if (str_starts_with($key, $prefix)) {
return true;
}
}
return false;
}
/**
* @param string $class
* @return array<string, Defer[]> method => Defer[]
*/
public static function getAll(string $class): array
{
$result = [];
$prefix = $class . '::';
foreach (self::$registry as $key => $defers) {
if (str_starts_with($key, $prefix)) {
$method = substr($key, strlen($prefix));
$result[$method] = $defers;
}
}
return $result;
}
/**
* 异步执行 Defer 回调 — 不阻塞父方法返回值
* 在子协程中执行所有已注册的回调,并注入 request/response 上下文
*
* @param string $class 原始类名
* @param string $method 原始方法名
*/
public static function execute(string $class, string $method): void
{
$key = self::key($class, $method);
if (!isset(self::$registry[$key])) {
return;
}
$defers = self::$registry[$key];
unset(self::$registry[$key]);
// 在创建子协程前捕获父协程的 request/response 上下文
// 子协程的 Coroutine Context 是空的,不继承父协程
$request = self::captureRequest();
$response = self::captureResponse();
// 非协程环境直接同步执行,避免协程永不调度导致回调丢失
if (Coroutine::getCid() <= 0) {
foreach ($defers as $defer) {
try {
self::invokeSingleDefer($defer, $request, $response);
} catch (\Throwable $throwable) {
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
}
}
return;
}
Coroutine::create(function () use ($defers, $request, $response) {
foreach ($defers as $defer) {
try {
self::invokeSingleDefer($defer, $request, $response);
} catch (\Throwable $throwable) {
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
}
}
});
}
/**
* 调用单个 Defer 回调
*
* @param Defer $defer 延迟回调配置
* @param ServerRequestInterface|null $request 请求上下文
* @param ResponseInterface|null $response 响应上下文
*/
private static function invokeSingleDefer(Defer $defer, ?ServerRequestInterface $request, ?ResponseInterface $response): void
{
$callback = $defer->callback;
$params = $defer->params;
if (is_array($callback)) {
[$class, $method] = $callback;
$instance = \Kiri::getDi()->get($class);
self::injectContext($instance, $request, $response);
call_user_func([$instance, $method], ...$params);
} else {
$instance = \Kiri::getDi()->get($callback);
self::injectContext($instance, $request, $response);
call_user_func([$instance, '__invoke'], ...$params);
}
}
/**
* 捕获当前协程的 request,不抛出异常
*/
private static function captureRequest(): ?ServerRequestInterface
{
try {
return \request();
} catch (\Throwable) {
return null;
}
}
/**
* 捕获当前协程的 response,不抛出异常
*/
private static function captureResponse(): ?ResponseInterface
{
try {
return \response();
} catch (\Throwable) {
return null;
}
}
/**
* 将 request/response 注入到回调实例
* 已有 #[Container] 注入的属性会被覆盖(子协程中 DI 注入的是错误上下文)
*
* @param object $instance 回调类实例
* @param ServerRequestInterface|null $request 请求上下文
* @param ResponseInterface|null $response 响应上下文
*/
private static function injectContext(
object $instance,
?ServerRequestInterface $request,
?ResponseInterface $response
): void {
try {
$reflect = new \ReflectionClass($instance);
if ($request !== null && $reflect->hasProperty('request')) {
$prop = $reflect->getProperty('request');
if ($prop->isPublic() || $prop->isProtected()) {
// $prop->setAccessible(true);
$prop->setValue($instance, $request);
}
}
if ($response !== null && $reflect->hasProperty('response')) {
$prop = $reflect->getProperty('response');
if ($prop->isPublic() || $prop->isProtected()) {
// $prop->setAccessible(true);
$prop->setValue($instance, $response);
}
}
} catch (\Throwable) {
// 注入失败不影响主流程
}
}
/**
* 移除指定类的所有 Defer 注册,用于热重载时清理失效类
* @param string $class
* @return void
*/
public static function removeClass(string $class): void
{
$prefix = $class . '::';
foreach (array_keys(self::$registry) as $key) {
if (str_starts_with($key, $prefix)) {
unset(self::$registry[$key]);
}
}
}
/**
* 获取注册表统计信息,用于内存监控
* @return array{totalKeys: int, totalDefer: int}
*/
public static function getStats(): array
{
$totalDefer = 0;
foreach (self::$registry as $defers) {
$totalDefer += count($defers);
}
return [
'totalKeys' => count(self::$registry),
'totalDefer' => $totalDefer,
];
}
/**
* @return void
*/
public static function clear(): void
{
self::$registry = [];
}
/**
* @param string $class
* @param string $method
* @return string
*/
private static function key(string $class, string $method): string
{
return $class . '::' . $method;
}
}
+159
View File
@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Kiri\Router\Defer;
use Kiri;
use Kiri\Router\Annotate\Defer;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use ReflectionClass;
use Swoole\Coroutine;
/**
* Defer 回调执行器 — 统一处理协程安全的上下文注入与异步执行
*/
class DeferExecutor
{
/**
* 执行一批 Defer 回调
*
* @param Defer[] $defers
*/
public static function run(array $defers): void
{
if (empty($defers)) {
return;
}
$request = self::captureRequest();
$response = self::captureResponse();
if (Coroutine::getCid() <= 0) {
self::executeSync($defers, $request, $response);
return;
}
Coroutine::create(function () use ($defers, $request, $response) {
self::executeSync($defers, $request, $response);
});
}
/**
* 为实例注入 request/response 上下文
*/
public static function inject(object $instance): object
{
try {
$request = self::captureRequest();
if ($request !== null) {
self::setProperty($instance, 'request', $request);
}
$response = self::captureResponse();
if ($response !== null) {
self::setProperty($instance, 'response', $response);
}
} catch (\Throwable) {
}
return $instance;
}
private static function executeSync(
array $defers,
?ServerRequestInterface $request,
?ResponseInterface $response
): void {
foreach ($defers as $defer) {
try {
self::invokeDefer($defer, $request, $response);
} catch (\Throwable $throwable) {
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
}
}
}
private static function invokeDefer(
Defer $defer,
?ServerRequestInterface $request,
?ResponseInterface $response
): void {
$callback = $defer->callback;
$params = $defer->params;
if (is_array($callback)) {
[$class, $method] = $callback;
$instance = self::resolveInstance($class, $request, $response);
call_user_func([$instance, $method], ...$params);
} else {
$instance = self::resolveInstance($callback, $request, $response);
call_user_func([$instance, '__invoke'], ...$params);
}
}
private static function resolveInstance(
string $class,
?ServerRequestInterface $request,
?ResponseInterface $response
): object {
$instance = Kiri::getDi()->get($class);
if ($instance instanceof DeferHandler) {
if ($request !== null) {
$instance->request = $request;
}
if ($response !== null) {
$instance->response = $response;
}
return $instance;
}
if ($request !== null) {
self::setProperty($instance, 'request', $request);
}
if ($response !== null) {
self::setProperty($instance, 'response', $response);
}
return $instance;
}
private static function setProperty(object $instance, string $name, mixed $value): void
{
try {
$reflect = new ReflectionClass($instance);
if (!$reflect->hasProperty($name)) return;
$prop = $reflect->getProperty($name);
if ($prop->isStatic()) return;
$prop->setAccessible(true);
$prop->setValue($instance, $value);
} catch (\Throwable) {
}
}
private static function captureRequest(): ?ServerRequestInterface
{
try {
if (function_exists('request')) return \request();
} catch (\Throwable) {}
return null;
}
private static function captureResponse(): ?ResponseInterface
{
try {
if (function_exists('response')) return \response();
} catch (\Throwable) {}
return null;
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Kiri\Router\Defer;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* Defer 回调基类 — 提供 request/response 上下文属性
*
* 所有需要在 #[Defer] 回调中访问请求上下文的类应继承此类。
* DeferExecutor 会自动将父协程的 request/response 注入到这两个属性。
*/
abstract class DeferHandler
{
/** @var ServerRequestInterface 当前请求上下文 (DeferExecutor 自动注入) */
public ServerRequestInterface $request;
/** @var ResponseInterface 当前响应上下文 (DeferExecutor 自动注入) */
public ResponseInterface $response;
}
@@ -1,7 +1,7 @@
<?php
declare(strict_types=1);
namespace Kiri\Router\Annotate;
namespace Kiri\Router\Defer;
use PhpParser\Node;
use PhpParser\Node\Stmt;
@@ -18,9 +18,6 @@ class DeferProxyGenerator
/**
* @param string $className
* @param array $construct
* @return object
* @throws \ReflectionException
*/
public static function create(string $className, array $construct): object
@@ -59,8 +56,6 @@ class DeferProxyGenerator
/**
* @param string $className
* @return string
* @throws \ReflectionException
*/
private static function generate(string $className): string
@@ -70,32 +65,20 @@ class DeferProxyGenerator
$stmts = [];
foreach ($methods as $methodName => $defers) {
if (!$reflect->hasMethod($methodName)) {
continue;
}
if (!$reflect->hasMethod($methodName)) continue;
$method = $reflect->getMethod($methodName);
if ($method->isPrivate() || $method->isStatic() || $method->isFinal() || $method->isConstructor() || $method->isDestructor()) {
continue;
}
if ($method->isPrivate() || $method->isStatic() || $method->isFinal() || $method->isConstructor() || $method->isDestructor()) continue;
$stmts[] = self::buildMethod($method);
}
if (empty($stmts)) {
return '';
}
if (empty($stmts)) return '';
$classNode = new Stmt\Class_(
new Name($className . '__DeferProxy'),
[
'extends' => new Name\FullyQualified($className),
'stmts' => $stmts,
]
['extends' => new Name\FullyQualified($className), 'stmts' => $stmts]
);
$namespace = $reflect->getNamespaceName();
$namespaceNode = new Stmt\Namespace_(
$namespace !== '' ? new Name($namespace) : null,
[$classNode]
@@ -106,10 +89,6 @@ class DeferProxyGenerator
}
/**
* @param ReflectionMethod $method
* @return Stmt\ClassMethod
*/
private static function buildMethod(ReflectionMethod $method): Stmt\ClassMethod
{
$methodName = $method->getName();
@@ -130,16 +109,12 @@ class DeferProxyGenerator
$var = new Expr\Variable($param->getName());
$params[] = new Node\Param(
$var,
$default,
$type,
$params[] = new Node\Param($var, $default, $type,
byRef: $param->isPassedByReference(),
variadic: $param->isVariadic()
);
$args[] = new Node\Arg(
$var,
$args[] = new Node\Arg($var,
byRef: $param->isPassedByReference(),
unpack: $param->isVariadic()
);
@@ -151,19 +126,12 @@ class DeferProxyGenerator
$returnType = new Name($refReturnType->getName());
}
$parentCall = new Expr\StaticCall(
new Name('parent'),
$methodName,
$args
);
$stmts = [
new Stmt\Expression(new Expr\Assign(new Expr\Variable('result'), $parentCall)),
new Stmt\Expression(new Expr\Assign(new Expr\Variable('result'),
new Expr\StaticCall(new Name('parent'), $methodName, $args))),
new Stmt\Expression(
new Expr\StaticCall(
new Name\FullyQualified(DeferRegistry::class),
'execute',
[
new Name\FullyQualified(DeferRegistry::class), 'execute', [
new Node\Arg(new Expr\ClassConstFetch(new Name\FullyQualified($method->getDeclaringClass()->getName()), 'class')),
new Node\Arg(new Node\Scalar\String_($methodName)),
]
@@ -172,66 +140,49 @@ class DeferProxyGenerator
new Stmt\Return_(new Expr\Variable('result')),
];
return new Stmt\ClassMethod(
$methodName,
[
'flags' => $method->isPublic() ? Stmt\Class_::MODIFIER_PUBLIC : Stmt\Class_::MODIFIER_PROTECTED,
'params' => $params,
'returnType' => $returnType,
'stmts' => $stmts,
]
);
return new Stmt\ClassMethod($methodName, [
'flags' => $method->isPublic() ? Stmt\Class_::MODIFIER_PUBLIC : Stmt\Class_::MODIFIER_PROTECTED,
'params' => $params,
'returnType' => $returnType,
'stmts' => $stmts,
]);
}
/**
* @param \ReflectionParameter $param
* @return Node\Expr
*/
private static function buildDefaultValue(\ReflectionParameter $param): Node\Expr
{
if (!$param->isDefaultValueAvailable()) {
return new Expr\ConstFetch(new Name('null'));
}
$value = $param->getDefaultValue();
return match (true) {
is_bool($value) => new Expr\ConstFetch(new Name($value ? 'true' : 'false')),
is_int($value) => new Node\Scalar\LNumber($value),
is_float($value) => new Node\Scalar\DNumber($value),
is_string($value)=> new Node\Scalar\String_($value),
is_array($value) => new Expr\Array_(
array_map(fn($k, $v) => new Expr\ArrayItem(
is_bool($value) => new Expr\ConstFetch(new Name($value ? 'true' : 'false')),
is_int($value) => new Node\Scalar\LNumber($value),
is_float($value) => new Node\Scalar\DNumber($value),
is_string($value) => new Node\Scalar\String_($value),
is_array($value) => new Expr\Array_(array_map(
fn($k, $v) => new Expr\ArrayItem(
self::buildDefaultValueFromScalar($v),
is_string($k) ? new Node\Scalar\String_($k) : null
), array_keys($value), $value)
),
default => new Expr\ConstFetch(new Name('null')),
), array_keys($value), $value
)),
default => new Expr\ConstFetch(new Name('null')),
};
}
/**
* @param mixed $value
* @return Node\Expr
*/
private static function buildDefaultValueFromScalar(mixed $value): Node\Expr
{
return match (true) {
is_bool($value) => new Expr\ConstFetch(new Name($value ? 'true' : 'false')),
is_int($value) => new Node\Scalar\LNumber($value),
is_float($value) => new Node\Scalar\DNumber($value),
is_string($value)=> new Node\Scalar\String_($value),
default => new Expr\ConstFetch(new Name('null')),
is_bool($value) => new Expr\ConstFetch(new Name($value ? 'true' : 'false')),
is_int($value) => new Node\Scalar\LNumber($value),
is_float($value) => new Node\Scalar\DNumber($value),
is_string($value) => new Node\Scalar\String_($value),
default => new Expr\ConstFetch(new Name('null')),
};
}
/**
* @param string $className
* @return string|null
*/
private static function getCacheFile(string $className): ?string
{
if (self::$cacheDir === null) {
@@ -241,7 +192,6 @@ class DeferProxyGenerator
return null;
}
}
return self::$cacheDir . str_replace('\\', '_', $className) . '__DeferProxy.php';
}
+120
View File
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Kiri\Router\Defer;
use Kiri\Router\Annotate\Defer;
class DeferRegistry
{
/**
* @var array<string, Defer[]> "ClassName::method" => Defer[]
*/
private static array $registry = [];
public static function add(string $class, string $method, Defer $defer): void
{
$key = self::key($class, $method);
self::$registry[$key][] = $defer;
}
/**
* @return Defer[]
*/
public static function get(string $class, string $method): array
{
return self::$registry[self::key($class, $method)] ?? [];
}
public static function hasAny(string $class): bool
{
$prefix = $class . '::';
foreach (array_keys(self::$registry) as $key) {
if (str_starts_with($key, $prefix)) {
return true;
}
}
return false;
}
/**
* @return array<string, Defer[]> method => Defer[]
*/
public static function getAll(string $class): array
{
$result = [];
$prefix = $class . '::';
foreach (self::$registry as $key => $defers) {
if (str_starts_with($key, $prefix)) {
$method = substr($key, strlen($prefix));
$result[$method] = $defers;
}
}
return $result;
}
/**
* 异步执行 Defer 回调 — 委托 DeferExecutor 处理协程安全与上下文注入
*/
public static function execute(string $class, string $method): void
{
$key = self::key($class, $method);
if (!isset(self::$registry[$key])) {
return;
}
$defers = self::$registry[$key];
unset(self::$registry[$key]);
DeferExecutor::run($defers);
}
/**
* 移除指定类的所有 Defer 注册
*/
public static function removeClass(string $class): void
{
$prefix = $class . '::';
foreach (array_keys(self::$registry) as $key) {
if (str_starts_with($key, $prefix)) {
unset(self::$registry[$key]);
}
}
}
/**
* @return array{totalKeys: int, totalDefer: int}
*/
public static function getStats(): array
{
$totalDefer = 0;
foreach (self::$registry as $defers) {
$totalDefer += count($defers);
}
return [
'totalKeys' => count(self::$registry),
'totalDefer' => $totalDefer,
];
}
public static function clear(): void
{
self::$registry = [];
}
private static function key(string $class, string $method): string
{
return $class . '::' . $method;
}
}
+3 -95
View File
@@ -6,13 +6,13 @@ namespace Kiri\Router;
use Closure;
use Kiri;
use Kiri\Router\Annotate\Defer;
use Kiri\Router\Defer\DeferExecutor;
use Kiri\Router\Format\IFormat;
use Kiri\Router\Format\MixedFormat;
use Kiri\Router\Format\NoBody;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Swoole\Coroutine;
class Handler implements RequestHandlerInterface
{
@@ -197,8 +197,7 @@ class Handler implements RequestHandlerInterface
/**
* 异步执行 Defer 回调 — 不阻塞客户端响应
* 提前捕获当前协程的 request/response,新建协程后注入到回调实例
* 异步执行 Defer 回调 — 委托 DeferExecutor 处理协程安全与上下文注入
*/
private function executeDeferred(): void
{
@@ -209,98 +208,7 @@ class Handler implements RequestHandlerInterface
$defers = $this->deferred;
$this->deferred = [];
// 在创建子协程前捕获父协程的 request/response 上下文
// 子协程的 Coroutine Context 是空的,不继承父协程
$request = \request();
$response = \response();
// 非协程环境直接同步执行,避免协程永不调度导致回调丢失
if (Coroutine::getCid() <= 0) {
foreach ($defers as $defer) {
try {
self::invokeDeferCallback($defer, $request, $response);
} catch (\Throwable $throwable) {
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
}
}
return;
}
Coroutine::create(function () use ($defers, $request, $response) {
foreach ($defers as $defer) {
try {
self::invokeDeferCallback($defer, $request, $response);
} catch (\Throwable $throwable) {
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
}
}
});
}
/**
* 调用单个 Defer 回调,自动注入 request/response 到回调实例
*
* @param Defer $defer 延迟回调配置
* @param ServerRequestInterface $request 当前请求
* @param ResponseInterface $response 当前响应
*/
private static function invokeDeferCallback(
Defer $defer,
ServerRequestInterface $request,
ResponseInterface $response
): void {
$callback = $defer->callback;
$params = $defer->params;
if (is_array($callback)) {
[$class, $method] = $callback;
$instance = Kiri::getDi()->get($class);
self::injectContextToInstance($instance, $request, $response);
call_user_func([$instance, $method], ...$params);
} else {
$instance = Kiri::getDi()->get($callback);
self::injectContextToInstance($instance, $request, $response);
call_user_func([$instance, '__invoke'], ...$params);
}
}
/**
* 将 request/response 注入到回调实例
* 通过反射为实例设置 request 和 response 属性
* 已有 #[Container] 注入的属性会被覆盖(子协程中 DI 注入的是错误上下文)
*
* @param object $instance 回调类实例
* @param ServerRequestInterface $request 当前请求
* @param ResponseInterface $response 当前响应
*/
private static function injectContextToInstance(
object $instance,
ServerRequestInterface $request,
ResponseInterface $response
): void {
try {
$reflect = new \ReflectionClass($instance);
if ($reflect->hasProperty('request')) {
$prop = $reflect->getProperty('request');
if ($prop->isPublic() || $prop->isProtected()) {
$prop->setAccessible(true);
$prop->setValue($instance, $request);
}
}
if ($reflect->hasProperty('response')) {
$prop = $reflect->getProperty('response');
if ($prop->isPublic() || $prop->isProtected()) {
$prop->setAccessible(true);
$prop->setValue($instance, $response);
}
}
} catch (\Throwable) {
// 注入失败不影响主流程
}
DeferExecutor::run($defers);
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace Kiri\Router;
use Closure;
use Kiri\Router\Annotate\Defer;
use Kiri\Router\Annotate\DeferRegistry;
use Kiri\Router\Defer\DeferRegistry;
use Kiri\Router\Base\NotFoundController;
use Kiri\Router\Constrict\RequestMethod;
use Psr\Http\Server\MiddlewareInterface;