Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48298ef1f7 | |||
| d4a1e9c8d7 | |||
| f9ac567bfe | |||
| be7c5da071 | |||
| fec0715c40 | |||
| 2239a34681 | |||
| 3daa4021ec | |||
| 7455dc8d58 | |||
| 5a1aa2d60f | |||
| e40db9d1fb | |||
| b5e0026816 | |||
| 69804ea595 | |||
| f099ca8402 | |||
| 0087115bdc | |||
| fead1203be | |||
| 28973df8b6 |
@@ -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
-1
@@ -9,7 +9,7 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.4",
|
"php": ">=8.5",
|
||||||
"composer-runtime-api": "^2.0",
|
"composer-runtime-api": "^2.0",
|
||||||
"psr/http-server-middleware": "^1.0",
|
"psr/http-server-middleware": "^1.0",
|
||||||
"psr/http-message": "^1.0"
|
"psr/http-message": "^1.0"
|
||||||
|
|||||||
Generated
+233
@@ -0,0 +1,233 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "3a858a1006b21751883a8dcfbd184549",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "lovefc/eztpl",
|
||||||
|
"version": "1.7.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/lovefc/eztpl.git",
|
||||||
|
"reference": "bb45c458b7522fae3fd6a5687db7b7d227c41b43"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/lovefc/eztpl/zipball/bb45c458b7522fae3fd6a5687db7b7d227c41b43",
|
||||||
|
"reference": "bb45c458b7522fae3fd6a5687db7b7d227c41b43",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"lovefc\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "lovefc",
|
||||||
|
"homepage": "http://lovefc.cn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "一个小型高效的PHP单文件模板引擎",
|
||||||
|
"homepage": "https://github.com/lovefc/eztpl.git",
|
||||||
|
"keywords": [
|
||||||
|
"php",
|
||||||
|
"template"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/lovefc/eztpl/issues",
|
||||||
|
"source": "https://github.com/lovefc/eztpl/tree/1.7.0"
|
||||||
|
},
|
||||||
|
"time": "2023-10-21T06:04:46+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-message",
|
||||||
|
"version": "1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-message.git",
|
||||||
|
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||||
|
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Message\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "http://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP messages",
|
||||||
|
"homepage": "https://github.com/php-fig/http-message",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-message",
|
||||||
|
"psr",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/http-message/tree/1.1"
|
||||||
|
},
|
||||||
|
"time": "2023-04-04T09:50:52+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-server-handler",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-server-handler.git",
|
||||||
|
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||||
|
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.0",
|
||||||
|
"psr/http-message": "^1.0 || ^2.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Server\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP server-side request handler",
|
||||||
|
"keywords": [
|
||||||
|
"handler",
|
||||||
|
"http",
|
||||||
|
"http-interop",
|
||||||
|
"psr",
|
||||||
|
"psr-15",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
|
||||||
|
},
|
||||||
|
"time": "2023-04-10T20:06:20+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-server-middleware",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-server-middleware.git",
|
||||||
|
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||||
|
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.0",
|
||||||
|
"psr/http-message": "^1.0 || ^2.0",
|
||||||
|
"psr/http-server-handler": "^1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Server\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP server-side middleware",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-interop",
|
||||||
|
"middleware",
|
||||||
|
"psr",
|
||||||
|
"psr-15",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/php-fig/http-server-middleware/issues",
|
||||||
|
"source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2"
|
||||||
|
},
|
||||||
|
"time": "2023-04-11T06:14:47+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {
|
||||||
|
"php": ">=8.4",
|
||||||
|
"composer-runtime-api": "^2.0"
|
||||||
|
},
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.2.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|array $callback Class name or [Class::class, 'method']
|
||||||
|
* @param array $params
|
||||||
|
*/
|
||||||
|
public function __construct(readonly public string|array $callback, readonly public array $params = [])
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function dispatch(string $class, string $method): void
|
||||||
|
{
|
||||||
|
DeferRegistry::add($class, $method, $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -97,7 +97,8 @@ class ConstrictResponse extends Message implements ResponseInterface
|
|||||||
*/
|
*/
|
||||||
public function json(array $content, int $statusCode = 200): static
|
public function json(array $content, int $statusCode = 200): static
|
||||||
{
|
{
|
||||||
$this->stream->write(json_encode($content, JSON_UNESCAPED_UNICODE));
|
$encoded = json_encode($content, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
||||||
|
$this->stream->write($encoded === false ? '{"error":"json encode failed"}' : $encoded);
|
||||||
return $this->withContentType(ContentType::JSON)->withStatus($statusCode);
|
return $this->withContentType(ContentType::JSON)->withStatus($statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Kiri\Router\Defer;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
use PhpParser\PrettyPrinter\Standard;
|
||||||
|
use ReflectionClass;
|
||||||
|
use ReflectionMethod;
|
||||||
|
|
||||||
|
class DeferProxyGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
private static ?string $cacheDir = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \ReflectionException
|
||||||
|
*/
|
||||||
|
public static function create(string $className, array $construct): object
|
||||||
|
{
|
||||||
|
$proxyClass = $className . '__DeferProxy';
|
||||||
|
$cacheFile = self::getCacheFile($className);
|
||||||
|
|
||||||
|
if (!class_exists($proxyClass, false)) {
|
||||||
|
if ($cacheFile !== null && file_exists($cacheFile)) {
|
||||||
|
require_once $cacheFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists($proxyClass, false)) {
|
||||||
|
$code = self::generate($className);
|
||||||
|
if ($cacheFile !== null) {
|
||||||
|
$dir = dirname($cacheFile);
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
mkdir($dir, 0755, true);
|
||||||
|
}
|
||||||
|
file_put_contents($cacheFile, '<?php ' . $code);
|
||||||
|
require_once $cacheFile;
|
||||||
|
} else {
|
||||||
|
eval($code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_exists($proxyClass, false)) {
|
||||||
|
$reflect = new ReflectionClass($proxyClass);
|
||||||
|
return $reflect->newInstanceArgs($construct);
|
||||||
|
}
|
||||||
|
|
||||||
|
$reflect = new ReflectionClass($className);
|
||||||
|
return $reflect->newInstanceArgs($construct);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \ReflectionException
|
||||||
|
*/
|
||||||
|
private static function generate(string $className): string
|
||||||
|
{
|
||||||
|
$reflect = new ReflectionClass($className);
|
||||||
|
$methods = DeferRegistry::getAll($className);
|
||||||
|
$stmts = [];
|
||||||
|
|
||||||
|
foreach ($methods as $methodName => $defers) {
|
||||||
|
if (!$reflect->hasMethod($methodName)) continue;
|
||||||
|
$method = $reflect->getMethod($methodName);
|
||||||
|
if ($method->isPrivate() || $method->isStatic() || $method->isFinal() || $method->isConstructor() || $method->isDestructor()) continue;
|
||||||
|
$stmts[] = self::buildMethod($method);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($stmts)) return '';
|
||||||
|
|
||||||
|
$classNode = new Stmt\Class_(
|
||||||
|
new Name($className . '__DeferProxy'),
|
||||||
|
['extends' => new Name\FullyQualified($className), 'stmts' => $stmts]
|
||||||
|
);
|
||||||
|
|
||||||
|
$namespace = $reflect->getNamespaceName();
|
||||||
|
$namespaceNode = new Stmt\Namespace_(
|
||||||
|
$namespace !== '' ? new Name($namespace) : null,
|
||||||
|
[$classNode]
|
||||||
|
);
|
||||||
|
|
||||||
|
$printer = new Standard();
|
||||||
|
return $printer->prettyPrintFile([$namespaceNode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static function buildMethod(ReflectionMethod $method): Stmt\ClassMethod
|
||||||
|
{
|
||||||
|
$methodName = $method->getName();
|
||||||
|
$params = [];
|
||||||
|
$args = [];
|
||||||
|
|
||||||
|
foreach ($method->getParameters() as $param) {
|
||||||
|
$type = null;
|
||||||
|
$refType = $param->getType();
|
||||||
|
if ($refType instanceof \ReflectionNamedType) {
|
||||||
|
$type = new Name($refType->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
$default = null;
|
||||||
|
if ($param->isDefaultValueAvailable()) {
|
||||||
|
$default = self::buildDefaultValue($param);
|
||||||
|
}
|
||||||
|
|
||||||
|
$var = new Expr\Variable($param->getName());
|
||||||
|
|
||||||
|
$params[] = new Node\Param($var, $default, $type,
|
||||||
|
byRef: $param->isPassedByReference(),
|
||||||
|
variadic: $param->isVariadic()
|
||||||
|
);
|
||||||
|
|
||||||
|
$args[] = new Node\Arg($var,
|
||||||
|
byRef: $param->isPassedByReference(),
|
||||||
|
unpack: $param->isVariadic()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnType = null;
|
||||||
|
$refReturnType = $method->getReturnType();
|
||||||
|
if ($refReturnType instanceof \ReflectionNamedType) {
|
||||||
|
$returnType = new Name($refReturnType->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmts = [
|
||||||
|
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 Node\Arg(new Expr\ClassConstFetch(new Name\FullyQualified($method->getDeclaringClass()->getName()), 'class')),
|
||||||
|
new Node\Arg(new Node\Scalar\String_($methodName)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(
|
||||||
|
self::buildDefaultValueFromScalar($v),
|
||||||
|
is_string($k) ? new Node\Scalar\String_($k) : null
|
||||||
|
), array_keys($value), $value
|
||||||
|
)),
|
||||||
|
default => new Expr\ConstFetch(new Name('null')),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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')),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static function getCacheFile(string $className): ?string
|
||||||
|
{
|
||||||
|
if (self::$cacheDir === null) {
|
||||||
|
if (defined('APP_PATH')) {
|
||||||
|
self::$cacheDir = APP_PATH . 'runtime/proxies/';
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self::$cacheDir . str_replace('\\', '_', $className) . '__DeferProxy.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,7 +25,8 @@ class ArrayFormat implements IFormat
|
|||||||
*/
|
*/
|
||||||
public function call($result): ResponseInterface
|
public function call($result): ResponseInterface
|
||||||
{
|
{
|
||||||
return $this->response->withBody(new Stream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
$encoded = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
||||||
|
return $this->response->withBody(new Stream($encoded === false ? '[]' : $encoded));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ class MixedFormat implements IFormat
|
|||||||
return $this->response->withBody(new Stream('[object]'));
|
return $this->response->withBody(new Stream('[object]'));
|
||||||
}
|
}
|
||||||
if (is_array($result)) {
|
if (is_array($result)) {
|
||||||
return $this->response->withBody(new Stream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
$encoded = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
||||||
|
return $this->response->withBody(new Stream($encoded === false ? '[]' : $encoded));
|
||||||
} else {
|
} else {
|
||||||
return $this->response->withBody(new Stream((string)$result));
|
return $this->response->withBody(new Stream((string)$result));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ namespace Kiri\Router;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Kiri;
|
use Kiri;
|
||||||
|
use Kiri\Router\Annotate\Defer;
|
||||||
|
use Kiri\Router\Defer\DeferExecutor;
|
||||||
use Kiri\Router\Format\IFormat;
|
use Kiri\Router\Format\IFormat;
|
||||||
use Kiri\Router\Format\MixedFormat;
|
use Kiri\Router\Format\MixedFormat;
|
||||||
use Kiri\Router\Format\NoBody;
|
use Kiri\Router\Format\NoBody;
|
||||||
@@ -32,6 +34,11 @@ class Handler implements RequestHandlerInterface
|
|||||||
*/
|
*/
|
||||||
protected array $middlewares = [];
|
protected array $middlewares = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Defer[]
|
||||||
|
*/
|
||||||
|
protected array $deferred = [];
|
||||||
|
|
||||||
protected ?string $sourceFile = null;
|
protected ?string $sourceFile = null;
|
||||||
|
|
||||||
protected string $sourceKind = 'attribute';
|
protected string $sourceKind = 'attribute';
|
||||||
@@ -152,6 +159,25 @@ class Handler implements RequestHandlerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Defer[] $deferred
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setDeferred(array $deferred): void
|
||||||
|
{
|
||||||
|
$this->deferred = $deferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Defer[]
|
||||||
|
*/
|
||||||
|
public function getDeferred(): array
|
||||||
|
{
|
||||||
|
return $this->deferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ServerRequestInterface $request
|
* @param ServerRequestInterface $request
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
@@ -163,8 +189,26 @@ class Handler implements RequestHandlerInterface
|
|||||||
|
|
||||||
$data = call_user_func([$controller, $this->handler[1]], ...$this->parameters);
|
$data = call_user_func([$controller, $this->handler[1]], ...$this->parameters);
|
||||||
|
|
||||||
|
$this->executeDeferred();
|
||||||
|
|
||||||
/** 根据返回类型 */
|
/** 根据返回类型 */
|
||||||
return $this->format->call($data);
|
return $this->format->call($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步执行 Defer 回调 — 委托 DeferExecutor 处理协程安全与上下文注入
|
||||||
|
*/
|
||||||
|
private function executeDeferred(): void
|
||||||
|
{
|
||||||
|
if (empty($this->deferred)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defers = $this->deferred;
|
||||||
|
$this->deferred = [];
|
||||||
|
|
||||||
|
DeferExecutor::run($defers);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use PhpParser\Error;
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\NodeDumper;
|
|
||||||
use PhpParser\NodeFactory;
|
|
||||||
use PhpParser\NodeTraverser;
|
|
||||||
use PhpParser\NodeVisitorAbstract;
|
|
||||||
use PhpParser\ParserFactory;
|
|
||||||
use PhpParser\PrettyPrinter\Standard;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
|
|
||||||
class MiddlewareProxyGenerator {
|
|
||||||
private static string $cacheFile = __DIR__ . '/middleware_proxy.php';
|
|
||||||
private static NodeFactory $factory;
|
|
||||||
|
|
||||||
public static function generateProxy(array $middlewares): string {
|
|
||||||
self::$factory = new NodeFactory();
|
|
||||||
|
|
||||||
// 创建类结构(v5.5.0兼容写法)
|
|
||||||
$class = new Node\Stmt\Class_(
|
|
||||||
'MiddlewareProxy',
|
|
||||||
[
|
|
||||||
'implements' => [new Node\Name\FullyQualified(\Psr\Http\Server\MiddlewareInterface::class)],
|
|
||||||
'stmts' => []
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 生成中间件调用链
|
|
||||||
$method = self::createMethodNode($middlewares);
|
|
||||||
$class->stmts[] = $method;
|
|
||||||
|
|
||||||
// 生成代码
|
|
||||||
$printer = new Standard();
|
|
||||||
return $printer->prettyPrintFile([$class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function createMethodNode(array $middlewares): Node\Stmt\Function_ {
|
|
||||||
$method = new Node\Stmt\Function_(
|
|
||||||
'process',
|
|
||||||
[
|
|
||||||
'params' => [
|
|
||||||
new Node\Param(
|
|
||||||
new Node\Expr\Variable('request'),
|
|
||||||
null,
|
|
||||||
new Node\UnionType([new Node\Expr\Variable(\Psr\Http\Message\ServerRequestInterface::class)])
|
|
||||||
),
|
|
||||||
new Node\Param(
|
|
||||||
new Node\Expr\Variable('handler'),
|
|
||||||
null,
|
|
||||||
new Node\UnionType([new Node\Expr\Variable(\Psr\Http\Server\RequestHandlerInterface::class)])
|
|
||||||
)
|
|
||||||
],
|
|
||||||
'returnType' => new Node\Name(ResponseInterface::class),
|
|
||||||
'stmts' => []
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 生成中间件调用链(v5.5.0闭包写法)
|
|
||||||
$chain = [];
|
|
||||||
$count = count($middlewares);
|
|
||||||
for ($i = 0; $i < $count; $i++) {
|
|
||||||
$className = $middlewares[$i]::class;
|
|
||||||
$chain[] = self::createMiddlewareCall($i, $className, $count);
|
|
||||||
}
|
|
||||||
|
|
||||||
$method->stmts = array_reverse($chain);
|
|
||||||
return $method;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function createMiddlewareCall(int $index, string $className, int $count): Node {
|
|
||||||
$mwVar = new Node\Expr\Variable("mw{$index}");
|
|
||||||
$nextHandler = new Node\Expr\Variable("next");
|
|
||||||
|
|
||||||
// 创建中间件实例(v5.5.0兼容)
|
|
||||||
$instanciate = new Node\Expr\New_($className);
|
|
||||||
|
|
||||||
// 创建闭包表达式(v5.5.0语法)
|
|
||||||
$closure = new Node\Expr\Closure([
|
|
||||||
'static' => true,
|
|
||||||
'params' => [new Node\Param($nextHandler)],
|
|
||||||
'stmts' => [
|
|
||||||
new Node\Stmt\Expression(
|
|
||||||
new Node\Expr\MethodCall(
|
|
||||||
$instanciate,
|
|
||||||
'process',
|
|
||||||
[new Node\Arg($nextHandler), new Node\Arg($mwVar)]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
return new Node\Stmt\Expression($closure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成代理类代码
|
|
||||||
$middlewares = [AuthMiddleware::class, LogMiddleware::class];
|
|
||||||
$proxyCode = MiddlewareProxyGenerator::generateProxy($middlewares);
|
|
||||||
|
|
||||||
// 写入代理类文件
|
|
||||||
file_put_contents(MiddlewareProxyGenerator::$cacheFile, $proxyCode);
|
|
||||||
@@ -6,6 +6,9 @@ namespace Kiri\Router;
|
|||||||
|
|
||||||
class RouteEntry
|
class RouteEntry
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param array $deferred Array of ['callback' => string|array, 'params' => array]
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly string $requestMethod,
|
public readonly string $requestMethod,
|
||||||
public readonly string $path,
|
public readonly string $path,
|
||||||
@@ -14,6 +17,7 @@ class RouteEntry
|
|||||||
public readonly array $middlewares = [],
|
public readonly array $middlewares = [],
|
||||||
public readonly ?string $sourceFile = null,
|
public readonly ?string $sourceFile = null,
|
||||||
public readonly string $sourceKind = 'attribute',
|
public readonly string $sourceKind = 'attribute',
|
||||||
|
public readonly array $deferred = [],
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+181
-5
@@ -39,6 +39,14 @@ class Router
|
|||||||
|
|
||||||
private static ?string $currentSourceFile = null;
|
private static ?string $currentSourceFile = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记首次完整扫描是否已完成
|
||||||
|
* Master 进程中完成扫描后设为 true,Worker 通过 fork 继承此标记
|
||||||
|
* Worker 启动时检查此标记,避免重复执行全量 app 目录扫描导致 OOM
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static bool $initialScanDone = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name
|
* @param string $name
|
||||||
@@ -186,14 +194,44 @@ class Router
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 扫描并构建路由表
|
||||||
|
*
|
||||||
|
* Master 进程:执行完整扫描(路由文件加载 + app 目录扫描 + DeferRegistry 注入)
|
||||||
|
* Worker 进程(首次启动):仅加载路由文件注册路由表,跳过全量 app 扫描
|
||||||
|
* Worker 进程(热重载):检测到文件变更时执行完整扫描流程
|
||||||
|
*
|
||||||
|
* 设计原因:
|
||||||
|
* - Master 已完成类加载和字节码编译,Worker 通过 fork 继承全部内存
|
||||||
|
* - Worker 重复执行 opcache_compile_file + invalidateClasses 不产生新信息
|
||||||
|
* - 在应用文件较多时(500+),每个 Worker 的全量扫描会消耗数百 MB 内存导致 OOM
|
||||||
|
*
|
||||||
* @throws
|
* @throws
|
||||||
*/
|
*/
|
||||||
public function scan_build_route(): void
|
public function scan_build_route(): void
|
||||||
{
|
{
|
||||||
$coordinator = CoordinatorManager::utility(Coordinator::WORKER_START);
|
$coordinator = CoordinatorManager::utility(Coordinator::WORKER_START);
|
||||||
$container = Kiri::getDi();
|
$container = Kiri::getDi();
|
||||||
|
|
||||||
|
$changedFiles = $container->get(HotReloadState::class)->consume();
|
||||||
|
|
||||||
|
// Worker 首次启动(无变更文件 + Master 已完成扫描):
|
||||||
|
// 重新 include 路由文件(Router::get/post 显式注册) + 基于 Master 扫描清单重建注解路由
|
||||||
|
// 避免 opcache_compile_file,仅用 Reflection 重建路由,内存开销极小
|
||||||
|
if (empty($changedFiles) && self::$initialScanDone) {
|
||||||
|
$container->get(DataGrip::class)->reset(static::$type);
|
||||||
|
$this->read_dir_file(APP_PATH . 'routes');
|
||||||
|
$this->rebuildAnnotationRoutes($container);
|
||||||
|
$this->reset($container);
|
||||||
|
$coordinator->done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记首次扫描完成(Master 首次启动或 Worker 热重载时执行到此)
|
||||||
|
self::$initialScanDone = true;
|
||||||
|
|
||||||
$container->get(DataGrip::class)->reset(static::$type);
|
$container->get(DataGrip::class)->reset(static::$type);
|
||||||
$scanner = $container->get(Kiri\Di\Scanner::class);
|
|
||||||
|
$scanner = $container->get(Kiri\Di\Scanner::class);
|
||||||
$artifactState = $container->get(RouteArtifactState::class);
|
$artifactState = $container->get(RouteArtifactState::class);
|
||||||
$scanConfig = array_merge(
|
$scanConfig = array_merge(
|
||||||
config('servers.reload.scan', []),
|
config('servers.reload.scan', []),
|
||||||
@@ -201,7 +239,6 @@ class Router
|
|||||||
);
|
);
|
||||||
$scanner->setConfig($scanConfig);
|
$scanner->setConfig($scanConfig);
|
||||||
|
|
||||||
$changedFiles = $container->get(HotReloadState::class)->consume();
|
|
||||||
$normalizedAppPath = str_replace('\\', '/', APP_PATH . 'app');
|
$normalizedAppPath = str_replace('\\', '/', APP_PATH . 'app');
|
||||||
$normalizedRoutePath = str_replace('\\', '/', APP_PATH . 'routes');
|
$normalizedRoutePath = str_replace('\\', '/', APP_PATH . 'routes');
|
||||||
$routeChanged = false;
|
$routeChanged = false;
|
||||||
@@ -225,9 +262,9 @@ class Router
|
|||||||
$usedArtifact = $router->importArtifact($artifact, $appChangedFiles);
|
$usedArtifact = $router->importArtifact($artifact, $appChangedFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$usedArtifact) {
|
// routes 目录中的显式路由文件必须每次重建路由表时重新 include。
|
||||||
$this->read_dir_file(APP_PATH . 'routes');
|
// route artifact 只加速注解路由,不能替代 routes/*.php 的注册副作用。
|
||||||
}
|
$this->read_dir_file(APP_PATH . 'routes');
|
||||||
|
|
||||||
if (!$routeChanged && !empty($appChangedFiles) && ($scanConfig['cache_enabled'] ?? false)) {
|
if (!$routeChanged && !empty($appChangedFiles) && ($scanConfig['cache_enabled'] ?? false)) {
|
||||||
$scanner->scanFiles($appChangedFiles, APP_PATH . 'app/', null, !$usedArtifact);
|
$scanner->scanFiles($appChangedFiles, APP_PATH . 'app/', null, !$usedArtifact);
|
||||||
@@ -258,6 +295,145 @@ class Router
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 Master 扫描清单重建注解路由(轻量级,无文件 I/O)
|
||||||
|
* 遍历 Scanner manifest 中的所有类,用 Reflection 重新发现 #[Route]/#[Get] 等注解
|
||||||
|
* 避免 Worker 重复执行 opcache_compile_file,但确保注解路由不丢失
|
||||||
|
*
|
||||||
|
* @param ContainerInterface $container
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function rebuildAnnotationRoutes(ContainerInterface $container): void
|
||||||
|
{
|
||||||
|
$scanner = $container->get(Kiri\Di\Scanner::class);
|
||||||
|
$scanConfig = array_merge(
|
||||||
|
config('servers.reload.scan', []),
|
||||||
|
config('site.scanner', [])
|
||||||
|
);
|
||||||
|
$scanner->setConfig($scanConfig);
|
||||||
|
|
||||||
|
// 从 manifest 获取 Master 扫描过的类
|
||||||
|
$manifestEntries = $scanner->getManifestClasses();
|
||||||
|
$manifestClasses = [];
|
||||||
|
foreach ($manifestEntries as $entry) {
|
||||||
|
if (is_array($entry) && isset($entry['classes'])) {
|
||||||
|
foreach ($entry['classes'] as $c) {
|
||||||
|
$manifestClasses[$c] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键:manifest 只包含 Scanner 通过 require_once 新发现的类
|
||||||
|
// 但路由文件加载时 $container->get(Controller) 会触发 autoload 提前加载类
|
||||||
|
// 导致 Scanner 的 require_once 变成 no-op,该类及其注解永久丢失
|
||||||
|
// 因此必须合并 get_declared_classes() 补扫所有已声明的用户空间类
|
||||||
|
$allDeclared = get_declared_classes();
|
||||||
|
foreach ($allDeclared as $class) {
|
||||||
|
$manifestClasses[$class] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($manifestClasses)) {
|
||||||
|
\Kiri::getLogger()->warning('Annotation route rebuild: no classes to process');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$routeCount = 0;
|
||||||
|
$classCount = 0;
|
||||||
|
$errorCount = 0;
|
||||||
|
$dispatchCount = 0;
|
||||||
|
|
||||||
|
// 只处理用户命名空间下的类,排除框架和 PHP 内置类
|
||||||
|
$userNamespaces = $scanConfig['user_namespaces'] ?? ['App\\'];
|
||||||
|
|
||||||
|
foreach (array_keys($manifestClasses) as $class) {
|
||||||
|
$isUserClass = false;
|
||||||
|
foreach ($userNamespaces as $ns) {
|
||||||
|
if (str_starts_with($class, $ns)) {
|
||||||
|
$isUserClass = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$isUserClass) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!class_exists($class)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$classCount++;
|
||||||
|
try {
|
||||||
|
$reflect = $container->getReflectionClass($class);
|
||||||
|
if (!$reflect->isInstantiable() || $reflect->isTrait() || $reflect->isEnum() || $reflect->isInterface() || $reflect->isAbstract()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($reflect->getMethods() as $method) {
|
||||||
|
if ($method->isStatic() || $method->getDeclaringClass()->getName() !== $class) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($method->getAttributes() as $attribute) {
|
||||||
|
$attrName = $attribute->getName();
|
||||||
|
if (!class_exists($attrName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$instance = $attribute->newInstance();
|
||||||
|
if ($instance instanceof Kiri\Di\Interface\InjectMethodInterface) {
|
||||||
|
$instance->dispatch($class, $method->getName());
|
||||||
|
$dispatchCount++;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$errorCount++;
|
||||||
|
\Kiri::getLogger()->error("Annotation rebuild error [{$class}::{$method->getName()} @ {$attrName}]: {$e->getMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$errorCount++;
|
||||||
|
\Kiri::getLogger()->error("Annotation rebuild class [{$class}]: {$e->getMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$router = $container->get(DataGrip::class)->get(static::$type);
|
||||||
|
$routeCount = count($router->dump());
|
||||||
|
|
||||||
|
\Kiri::getLogger()->info("Annotation route rebuild: {$classCount} user classes processed, {$dispatchCount} annotation routes dispatched, {$routeCount} total routes, {$errorCount} errors");
|
||||||
|
|
||||||
|
// 搜索特定路径的诊断日志
|
||||||
|
$searchPaths = ['/headers'];
|
||||||
|
foreach ($searchPaths as $searchPath) {
|
||||||
|
$found = [];
|
||||||
|
foreach (array_keys($manifestClasses) as $class) {
|
||||||
|
if (!class_exists($class)) continue;
|
||||||
|
try {
|
||||||
|
$reflect = $container->getReflectionClass($class);
|
||||||
|
foreach ($reflect->getMethods() as $method) {
|
||||||
|
foreach ($method->getAttributes() as $attr) {
|
||||||
|
if (in_array($attr->getName(), [
|
||||||
|
\Kiri\Router\Annotate\Get::class,
|
||||||
|
\Kiri\Router\Annotate\Post::class,
|
||||||
|
\Kiri\Router\Annotate\Put::class,
|
||||||
|
\Kiri\Router\Annotate\Delete::class,
|
||||||
|
\Kiri\Router\Annotate\Route::class,
|
||||||
|
])) {
|
||||||
|
$instance = $attr->newInstance();
|
||||||
|
$routePath = $instance->path ?? '';
|
||||||
|
if (str_contains($routePath, 'header')) {
|
||||||
|
$version = $instance->version ?? '';
|
||||||
|
$found[] = "{$class}::{$method->getName()} path={$routePath} version={$version}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {}
|
||||||
|
}
|
||||||
|
if (!empty($found)) {
|
||||||
|
\Kiri::getLogger()->info("Annotation route search '{$searchPath}': " . implode(' | ', $found));
|
||||||
|
} else {
|
||||||
|
\Kiri::getLogger()->warning("Annotation route search '{$searchPath}': NO annotation found in any class");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $path
|
* @param $path
|
||||||
*
|
*
|
||||||
|
|||||||
+79
-42
@@ -6,6 +6,8 @@ namespace Kiri\Router;
|
|||||||
|
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Kiri\Router\Annotate\Defer;
|
||||||
|
use Kiri\Router\Defer\DeferRegistry;
|
||||||
use Kiri\Router\Base\NotFoundController;
|
use Kiri\Router\Base\NotFoundController;
|
||||||
use Kiri\Router\Constrict\RequestMethod;
|
use Kiri\Router\Constrict\RequestMethod;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
@@ -80,16 +82,16 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
|
|
||||||
public function clear(): void
|
public function clear(): void
|
||||||
{
|
{
|
||||||
$this->_item = [];
|
$this->_item = [];
|
||||||
$this->dump = [];
|
$this->dump = [];
|
||||||
$this->groupTack = [];
|
$this->groupTack = [];
|
||||||
$this->methods = [];
|
$this->methods = [];
|
||||||
$this->httpHandler = [];
|
$this->httpHandler = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $method
|
* @param string $method
|
||||||
* @param HttpRequestHandler $handler
|
* @param HttpRequestHandler $handler
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -118,8 +120,8 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $method
|
* @param array $method
|
||||||
* @param string $route
|
* @param string $route
|
||||||
* @param string|array|Closure $closure
|
* @param string|array|Closure $closure
|
||||||
*/
|
*/
|
||||||
public function addRoute(array $method, string $route, string|array|Closure $closure): void
|
public function addRoute(array $method, string $route, string|array|Closure $closure): void
|
||||||
@@ -162,7 +164,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
$array[] = [
|
$array[] = [
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'method' => $method,
|
'method' => $method,
|
||||||
'handler' => $controller
|
'handler' => $controller,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return $array;
|
return $array;
|
||||||
@@ -170,7 +172,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string|array $closure
|
* @param string|array $closure
|
||||||
* @param ControllerInterpreter $interpreter
|
* @param ControllerInterpreter $interpreter
|
||||||
* @return Handler
|
* @return Handler
|
||||||
* @throws
|
* @throws
|
||||||
@@ -182,7 +184,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
if (is_string($closure[0])) {
|
if (is_string($closure[0])) {
|
||||||
$closure[0] = $container->get($closure[0]);
|
$closure[0] = $container->get($closure[0]);
|
||||||
}
|
}
|
||||||
$handler = $interpreter->addRouteByString(... $closure);
|
$handler = $interpreter->addRouteByString(... $closure);
|
||||||
$sourceFile = Router::getCurrentSourceFile();
|
$sourceFile = Router::getCurrentSourceFile();
|
||||||
if ($sourceFile !== null) {
|
if ($sourceFile !== null) {
|
||||||
$handler->setSourceFile($sourceFile);
|
$handler->setSourceFile($sourceFile);
|
||||||
@@ -194,8 +196,8 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
$closure .= '@';
|
$closure .= '@';
|
||||||
}
|
}
|
||||||
[$className, $method] = explode('@', $closure);
|
[$className, $method] = explode('@', $closure);
|
||||||
$class = $container->get($this->resetName($className));
|
$class = $container->get($this->resetName($className));
|
||||||
$handler = $interpreter->addRouteByString($class, $method);
|
$handler = $interpreter->addRouteByString($class, $method);
|
||||||
$sourceFile = Router::getCurrentSourceFile();
|
$sourceFile = Router::getCurrentSourceFile();
|
||||||
if ($sourceFile !== null) {
|
if ($sourceFile !== null) {
|
||||||
$handler->setSourceFile($sourceFile);
|
$handler->setSourceFile($sourceFile);
|
||||||
@@ -220,8 +222,8 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @param string $method
|
* @param string $method
|
||||||
* @param Handler $handler
|
* @param Handler $handler
|
||||||
* @return void
|
* @return void
|
||||||
* @throws
|
* @throws
|
||||||
@@ -236,12 +238,13 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
|
|
||||||
$this->methods[$method . '_' . $path] = $handler;
|
$this->methods[$method . '_' . $path] = $handler;
|
||||||
$handler->setMiddlewares($this->registerMiddleware($handler->getClass(), $handler->getMethod()));
|
$handler->setMiddlewares($this->registerMiddleware($handler->getClass(), $handler->getMethod()));
|
||||||
|
$handler->setDeferred(DeferRegistry::get($handler->getClass(), $handler->getMethod()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function exportArtifact(): array
|
public function exportArtifact(): array
|
||||||
{
|
{
|
||||||
$entries = [];
|
$entries = [];
|
||||||
$hasClosureRoutes = false;
|
$hasClosureRoutes = false;
|
||||||
|
|
||||||
foreach ($this->methods as $methodPath => $handler) {
|
foreach ($this->methods as $methodPath => $handler) {
|
||||||
@@ -251,26 +254,28 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$requestMethod, $path] = explode('_', $methodPath, 2);
|
[$requestMethod, $path] = explode('_', $methodPath, 2);
|
||||||
$class = $handler instanceof Handler ? $handler->getClass() : $handler->class;
|
$class = $handler instanceof Handler ? $handler->getClass() : $handler->class;
|
||||||
$method = $handler instanceof Handler ? $handler->getMethod() : $handler->method;
|
$method = $handler instanceof Handler ? $handler->getMethod() : $handler->method;
|
||||||
$middlewares = $handler instanceof Handler ? $handler->getMiddlewares() : $handler->middlewares;
|
$middlewares = $handler instanceof Handler ? $handler->getMiddlewares() : $handler->middlewares;
|
||||||
$sourceFile = $handler instanceof Handler ? $handler->getSourceFile() : $handler->sourceFile;
|
$sourceFile = $handler instanceof Handler ? $handler->getSourceFile() : $handler->sourceFile;
|
||||||
$sourceKind = $handler instanceof Handler ? $handler->getSourceKind() : $handler->sourceKind;
|
$sourceKind = $handler instanceof Handler ? $handler->getSourceKind() : $handler->sourceKind;
|
||||||
|
$deferred = $handler instanceof Handler ? $this->serializeDeferred($handler->getDeferred()) : ($handler->deferred ?? []);
|
||||||
|
|
||||||
$entries[] = [
|
$entries[] = [
|
||||||
'request_method' => $requestMethod,
|
'request_method' => $requestMethod,
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'class' => $class,
|
'class' => $class,
|
||||||
'method' => $method,
|
'method' => $method,
|
||||||
'middlewares' => $middlewares,
|
'middlewares' => $middlewares,
|
||||||
'source_file' => $sourceFile,
|
'source_file' => $sourceFile,
|
||||||
'source_kind' => $sourceKind,
|
'source_kind' => $sourceKind,
|
||||||
|
'deferred' => $deferred,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'has_closure_routes' => $hasClosureRoutes,
|
'has_closure_routes' => $hasClosureRoutes,
|
||||||
'entries' => $entries,
|
'entries' => $entries,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,24 +302,21 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$class = $entry['class'] ?? null;
|
$class = $entry['class'] ?? null;
|
||||||
$method = $entry['method'] ?? null;
|
$method = $entry['method'] ?? null;
|
||||||
$requestMethod = $entry['request_method'] ?? null;
|
$requestMethod = $entry['request_method'] ?? null;
|
||||||
$path = $entry['path'] ?? null;
|
$path = $entry['path'] ?? null;
|
||||||
|
|
||||||
if (!is_string($class) || !is_string($method) || !is_string($requestMethod) || !is_string($path)) {
|
if (!is_string($class) || !is_string($method) || !is_string($requestMethod) || !is_string($path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->methods[$requestMethod . '_' . $path] = new RouteEntry(
|
$middlewares = is_array($entry['middlewares'] ?? null) ? $entry['middlewares'] : [];
|
||||||
requestMethod: $requestMethod,
|
$sourceFile = is_string($sourceFile) ? $this->normalizePath($sourceFile) : null;
|
||||||
path: $path,
|
$sourceKind = is_string($entry['source_kind'] ?? null) ? $entry['source_kind'] : 'attribute';
|
||||||
class: $class,
|
$deferred = is_array($entry['deferred'] ?? null) ? $entry['deferred'] : [];
|
||||||
method: $method,
|
|
||||||
middlewares: is_array($entry['middlewares'] ?? null) ? $entry['middlewares'] : [],
|
$this->methods[$requestMethod . '_' . $path] = new RouteEntry(requestMethod: $requestMethod, path: $path, class: $class, method: $method, middlewares: $middlewares, sourceFile: $sourceFile, sourceKind: $sourceKind, deferred: $deferred);
|
||||||
sourceFile: is_string($sourceFile) ? $this->normalizePath($sourceFile) : null,
|
|
||||||
sourceKind: is_string($entry['source_kind'] ?? null) ? $entry['source_kind'] : 'attribute',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -342,7 +344,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $response
|
* @param array $response
|
||||||
* @param string $class
|
* @param string $class
|
||||||
* @param string $method
|
* @param string $method
|
||||||
* @return array
|
* @return array
|
||||||
@@ -375,8 +377,9 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
/**
|
/**
|
||||||
* @param array $response
|
* @param array $response
|
||||||
* @param array $middlewares
|
* @param array $middlewares
|
||||||
* @return array
|
* @return Defer[]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private function appendMiddleware(array $response, array $middlewares): array
|
private function appendMiddleware(array $response, array $middlewares): array
|
||||||
{
|
{
|
||||||
foreach ($middlewares as $middleware) {
|
foreach ($middlewares as $middleware) {
|
||||||
@@ -448,6 +451,39 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Defer[] $deferred
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function serializeDeferred(array $deferred): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($deferred as $defer) {
|
||||||
|
$result[] = [
|
||||||
|
'callback' => $defer->callback,
|
||||||
|
'params' => $defer->params,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $data
|
||||||
|
* @return Defer[]
|
||||||
|
*/
|
||||||
|
private function deserializeDeferred(array $data): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($data as $item) {
|
||||||
|
if (isset($item['callback'])) {
|
||||||
|
$result[] = new Defer($item['callback'], $item['params'] ?? []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private function normalizePath(string $path): string
|
private function normalizePath(string $path): string
|
||||||
{
|
{
|
||||||
$resolved = realpath($path) ?: $path;
|
$resolved = realpath($path) ?: $path;
|
||||||
@@ -459,11 +495,12 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
{
|
{
|
||||||
if ($method instanceof RouteEntry) {
|
if ($method instanceof RouteEntry) {
|
||||||
$controller = \Kiri::getDi()->get($method->class);
|
$controller = \Kiri::getDi()->get($method->class);
|
||||||
$handler = di(ControllerInterpreter::class)->addRouteByString($controller, $method->method);
|
$handler = di(ControllerInterpreter::class)->addRouteByString($controller, $method->method);
|
||||||
$handler->setRequestMethod($method->requestMethod);
|
$handler->setRequestMethod($method->requestMethod);
|
||||||
$handler->setMiddlewares($method->middlewares);
|
$handler->setMiddlewares($method->middlewares);
|
||||||
$handler->setSourceFile($method->sourceFile);
|
$handler->setSourceFile($method->sourceFile);
|
||||||
$handler->setSourceKind($method->sourceKind);
|
$handler->setSourceKind($method->sourceKind);
|
||||||
|
$handler->setDeferred($this->deserializeDeferred($method->deferred));
|
||||||
$method = $handler;
|
$method = $handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,7 +510,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
}
|
}
|
||||||
|
|
||||||
$requestHandler = new HttpRequestHandler($middlewares, $method);
|
$requestHandler = new HttpRequestHandler($middlewares, $method);
|
||||||
$validator = Middleware::getValidator($method->getClass(), $method->getMethod());
|
$validator = Middleware::getValidator($method->getClass(), $method->getMethod());
|
||||||
if ($validator !== null) {
|
if ($validator !== null) {
|
||||||
$requestHandler->withValidatorMiddleware(new ValidatorMiddleware(di(\Psr\Http\Message\ResponseInterface::class), $method->getClass(), $method->getMethod()));
|
$requestHandler->withValidatorMiddleware(new ValidatorMiddleware(di(\Psr\Http\Message\ResponseInterface::class), $method->getClass(), $method->getMethod()));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user