5 Commits

Author SHA1 Message Date
as2252258 48298ef1f7 eee 2026-07-03 14:56:04 +08:00
as2252258 d4a1e9c8d7 eee 2026-07-03 14:48:04 +08:00
as2252258 f9ac567bfe eee 2026-07-03 11:14:31 +08:00
as2252258 be7c5da071 eee 2026-06-28 20:20:21 +08:00
as2252258 fec0715c40 eee 2026-06-28 17:51:05 +08:00
9 changed files with 286 additions and 198 deletions
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace PHPSTORM_META {
registerArgumentsSet(
'router_actions',
\App\Controller\SiteController::class . '@globSetting'
);
expectedArguments(\Kiri\Router\Router::get(), 1, argumentsSet('router_actions'));
expectedArguments(\Kiri\Router\Router::post(), 1, argumentsSet('router_actions'));
}
+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);
+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,50 +140,37 @@ class DeferProxyGenerator
new Stmt\Return_(new Expr\Variable('result')),
];
return new Stmt\ClassMethod(
$methodName,
[
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_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)
),
), 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) {
@@ -228,10 +183,6 @@ class DeferProxyGenerator
}
/**
* @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';
}
@@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
namespace Kiri\Router\Annotate;
namespace Kiri\Router\Defer;
use Kiri\Router\Annotate\Defer;
class DeferRegistry
{
@@ -12,12 +14,6 @@ class DeferRegistry
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);
@@ -26,8 +22,6 @@ class DeferRegistry
/**
* @param string $class
* @param string $method
* @return Defer[]
*/
public static function get(string $class, string $method): array
@@ -36,10 +30,6 @@ class DeferRegistry
}
/**
* @param string $class
* @return bool
*/
public static function hasAny(string $class): bool
{
$prefix = $class . '::';
@@ -53,7 +43,6 @@ class DeferRegistry
/**
* @param string $class
* @return array<string, Defer[]> method => Defer[]
*/
public static function getAll(string $class): array
@@ -71,9 +60,7 @@ class DeferRegistry
/**
* @param string $class
* @param string $method
* @return void
* 异步执行 Defer 回调 委托 DeferExecutor 处理协程安全与上下文注入
*/
public static function execute(string $class, string $method): void
{
@@ -85,30 +72,12 @@ class DeferRegistry
$defers = self::$registry[$key];
unset(self::$registry[$key]);
foreach ($defers as $defer) {
try {
$callback = $defer->callback;
$params = $defer->params;
if (is_array($callback)) {
[$cbClass, $cbMethod] = $callback;
$instance = \Kiri::getDi()->get($cbClass);
call_user_func([$instance, $cbMethod], ...$params);
} else {
$instance = \Kiri::getDi()->get($callback);
call_user_func([$instance, '__invoke'], ...$params);
}
} catch (\Throwable $throwable) {
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
}
}
DeferExecutor::run($defers);
}
/**
* 移除指定类的所有 Defer 注册,用于热重载时清理失效类
* @param string $class
* @return void
* 移除指定类的所有 Defer 注册
*/
public static function removeClass(string $class): void
{
@@ -122,7 +91,6 @@ class DeferRegistry
/**
* 获取注册表统计信息,用于内存监控
* @return array{totalKeys: int, totalDefer: int}
*/
public static function getStats(): array
@@ -138,20 +106,12 @@ class DeferRegistry
}
/**
* @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;
+9 -17
View File
@@ -6,6 +6,7 @@ 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;
@@ -196,27 +197,18 @@ class Handler implements RequestHandlerInterface
/**
* @return void
* 异步执行 Defer 回调 — 委托 DeferExecutor 处理协程安全与上下文注入
*/
private function executeDeferred(): void
{
foreach ($this->deferred as $defer) {
try {
$callback = $defer->callback;
$params = $defer->params;
if (empty($this->deferred)) {
return;
}
if (is_array($callback)) {
[$class, $method] = $callback;
$instance = Kiri::getDi()->get($class);
call_user_func([$instance, $method], ...$params);
} else {
$instance = Kiri::getDi()->get($callback);
call_user_func([$instance, '__invoke'], ...$params);
}
} catch (\Throwable $throwable) {
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
}
}
$defers = $this->deferred;
$this->deferred = [];
DeferExecutor::run($defers);
}
}
+2 -2
View File
@@ -262,9 +262,9 @@ class Router
$usedArtifact = $router->importArtifact($artifact, $appChangedFiles);
}
if (!$usedArtifact) {
// routes 目录中的显式路由文件必须每次重建路由表时重新 include。
// route artifact 只加速注解路由,不能替代 routes/*.php 的注册副作用。
$this->read_dir_file(APP_PATH . 'routes');
}
if (!$routeChanged && !empty($appChangedFiles) && ($scanConfig['cache_enabled'] ?? false)) {
$scanner->scanFiles($appChangedFiles, APP_PATH . 'app/', null, !$usedArtifact);
+10 -14
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;
@@ -164,7 +164,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
$array[] = [
'path' => $path,
'method' => $method,
'handler' => $controller
'handler' => $controller,
];
}
return $array;
@@ -311,16 +311,12 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
continue;
}
$this->methods[$requestMethod . '_' . $path] = new RouteEntry(
requestMethod: $requestMethod,
path: $path,
class: $class,
method: $method,
middlewares: is_array($entry['middlewares'] ?? null) ? $entry['middlewares'] : [],
sourceFile: is_string($sourceFile) ? $this->normalizePath($sourceFile) : null,
sourceKind: is_string($entry['source_kind'] ?? null) ? $entry['source_kind'] : 'attribute',
deferred: is_array($entry['deferred'] ?? null) ? $entry['deferred'] : [],
);
$middlewares = is_array($entry['middlewares'] ?? null) ? $entry['middlewares'] : [];
$sourceFile = is_string($sourceFile) ? $this->normalizePath($sourceFile) : null;
$sourceKind = is_string($entry['source_kind'] ?? null) ? $entry['source_kind'] : 'attribute';
$deferred = is_array($entry['deferred'] ?? null) ? $entry['deferred'] : [];
$this->methods[$requestMethod . '_' . $path] = new RouteEntry(requestMethod: $requestMethod, path: $path, class: $class, method: $method, middlewares: $middlewares, sourceFile: $sourceFile, sourceKind: $sourceKind, deferred: $deferred);
}
return true;
@@ -379,8 +375,8 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
/**
* @param string $class
* @param string $method
* @param array $response
* @param array $middlewares
* @return Defer[]
*/