diff --git a/src/Annotate/Defer.php b/src/Annotate/Defer.php index 918193b..70371f3 100644 --- a/src/Annotate/Defer.php +++ b/src/Annotate/Defer.php @@ -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); diff --git a/src/Annotate/DeferRegistry.php b/src/Annotate/DeferRegistry.php deleted file mode 100644 index ef65bfa..0000000 --- a/src/Annotate/DeferRegistry.php +++ /dev/null @@ -1,263 +0,0 @@ - "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 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; - } - -} diff --git a/src/Defer/DeferExecutor.php b/src/Defer/DeferExecutor.php new file mode 100644 index 0000000..02ea286 --- /dev/null +++ b/src/Defer/DeferExecutor.php @@ -0,0 +1,159 @@ +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; + } + +} diff --git a/src/Defer/DeferHandler.php b/src/Defer/DeferHandler.php new file mode 100644 index 0000000..99854df --- /dev/null +++ b/src/Defer/DeferHandler.php @@ -0,0 +1,24 @@ + $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'; } diff --git a/src/Defer/DeferRegistry.php b/src/Defer/DeferRegistry.php new file mode 100644 index 0000000..0900bef --- /dev/null +++ b/src/Defer/DeferRegistry.php @@ -0,0 +1,120 @@ + "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 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; + } + +} diff --git a/src/Handler.php b/src/Handler.php index ad2d9d7..7136bb8 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -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); } } diff --git a/src/RouterCollector.php b/src/RouterCollector.php index f42e496..904b61b 100644 --- a/src/RouterCollector.php +++ b/src/RouterCollector.php @@ -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;