From 4cfd04c9887aa6ca24aaa2ba2000d48dca281d1f Mon Sep 17 00:00:00 2001 From: whwyy Date: Fri, 17 Apr 2026 16:30:52 +0800 Subject: [PATCH] eee --- src/DataGrip.php | 17 ++++ src/Handler.php | 28 +++++++ src/OnRequest.php | 5 ++ src/RouteArtifactState.php | 55 ++++++++++++ src/RouteEntry.php | 19 +++++ src/Router.php | 76 +++++++++++++---- src/RouterCollector.php | 168 +++++++++++++++++++++++++++++++++++-- 7 files changed, 346 insertions(+), 22 deletions(-) create mode 100644 src/RouteArtifactState.php create mode 100644 src/RouteEntry.php diff --git a/src/DataGrip.php b/src/DataGrip.php index 5b94fee..5a8cec5 100644 --- a/src/DataGrip.php +++ b/src/DataGrip.php @@ -25,4 +25,21 @@ class DataGrip } + public function reset(?string $type = null): void + { + if ($type === null) { + foreach ($this->servers as $server) { + if ($server instanceof RouterCollector) { + $server->clear(); + } + } + return; + } + + if (isset($this->servers[$type])) { + $this->servers[$type]->clear(); + } + } + + } diff --git a/src/Handler.php b/src/Handler.php index ed1d773..fe12a60 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -32,6 +32,10 @@ class Handler implements RequestHandlerInterface */ protected array $middlewares = []; + protected ?string $sourceFile = null; + + protected string $sourceKind = 'attribute'; + /** * @param array|Closure $handler * @param array $parameters @@ -124,6 +128,30 @@ class Handler implements RequestHandlerInterface } + public function setSourceFile(?string $sourceFile): void + { + $this->sourceFile = $sourceFile; + } + + + public function getSourceFile(): ?string + { + return $this->sourceFile; + } + + + public function setSourceKind(string $sourceKind): void + { + $this->sourceKind = $sourceKind; + } + + + public function getSourceKind(): string + { + return $this->sourceKind; + } + + /** * @param ServerRequestInterface $request * @return ResponseInterface diff --git a/src/OnRequest.php b/src/OnRequest.php index b77f23e..c8bed14 100644 --- a/src/OnRequest.php +++ b/src/OnRequest.php @@ -34,6 +34,9 @@ class OnRequest implements OnRequestInterface public RouterCollector $router; + public DataGrip $dataGrip; + + /** * @var ExceptionHandlerInterface */ @@ -59,6 +62,7 @@ class OnRequest implements OnRequestInterface */ public function __construct(public ResponseInterface $response, DataGrip $dataGrip) { + $this->dataGrip = $dataGrip; $this->responseEmitter = $this->response->emmit; $exception = \config('servers.request.exception'); if (!in_array(ExceptionHandlerInterface::class, class_implements($exception))) { @@ -82,6 +86,7 @@ class OnRequest implements OnRequestInterface /** @var CQ $PsrRequest */ Context::set(ResponseInterface::class, new ConstrictResponse($this->response->contentType)); $PsrRequest = Context::set(RequestInterface::class, CQ::builder($request)); + $this->router = $this->dataGrip->get(ROUTER_TYPE_HTTP); CoordinatorManager::utility(Coordinator::WORKER_START)->yield(); diff --git a/src/RouteArtifactState.php b/src/RouteArtifactState.php new file mode 100644 index 0000000..84bd6d1 --- /dev/null +++ b/src/RouteArtifactState.php @@ -0,0 +1,55 @@ + time(), + 'type' => $type, + 'artifact' => $artifact, + ]; + + $directory = dirname($this->getFilePath($type)); + if (!is_dir($directory)) { + mkdir($directory, 0755, true); + } + + file_put_contents($this->getFilePath($type), json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } + + public function load(string $type): array + { + $file = $this->getFilePath($type); + if (!file_exists($file)) { + return []; + } + + $data = json_decode((string)file_get_contents($file), true); + if (!is_array($data)) { + return []; + } + + return is_array($data['artifact'] ?? null) ? $data['artifact'] : []; + } + + public function has(string $type): bool + { + return file_exists($this->getFilePath($type)); + } + + private function getFilePath(string $type): string + { + $basePath = realpath($_SERVER['PWD'] ?? APP_PATH ?? getcwd()) ?: ($_SERVER['PWD'] ?? APP_PATH ?? getcwd()); + $basePath = str_replace('\\', '/', $basePath); + $runtimePath = defined('APP_PATH') + ? rtrim(str_replace('\\', '/', APP_PATH), '/') . '/storage/.kiri-route-artifacts/' + : sys_get_temp_dir() . '/kiri-route-artifacts/'; + + return $runtimePath . md5($basePath . '::' . $type) . '.json'; + } +} diff --git a/src/RouteEntry.php b/src/RouteEntry.php new file mode 100644 index 0000000..0d1eff3 --- /dev/null +++ b/src/RouteEntry.php @@ -0,0 +1,19 @@ +read_dir_file(APP_PATH . 'routes'); - $container = Kiri::getDi(); + $container->get(DataGrip::class)->reset(static::$type); $scanner = $container->get(Kiri\Di\Scanner::class); - $scanner->scan(APP_PATH . 'app/'); + $artifactState = $container->get(RouteArtifactState::class); + $scanConfig = array_merge( + config('servers.reload.scan', []), + config('site.scanner', []) + ); + $scanner->setConfig($scanConfig); + + $changedFiles = $container->get(HotReloadState::class)->consume(); + $normalizedAppPath = str_replace('\\', '/', APP_PATH . 'app'); + $normalizedRoutePath = str_replace('\\', '/', APP_PATH . 'routes'); + $routeChanged = false; + $appChangedFiles = []; + + foreach ($changedFiles as $changedFile) { + if (str_starts_with($changedFile, $normalizedRoutePath . '/')) { + $routeChanged = true; + continue; + } + + if (str_starts_with($changedFile, $normalizedAppPath . '/')) { + $appChangedFiles[] = $changedFile; + } + } + + $usedArtifact = false; + if (($scanConfig['cache_enabled'] ?? false) && !$routeChanged && $artifactState->has(static::$type)) { + $artifact = $artifactState->load(static::$type); + $router = $container->get(DataGrip::class)->get(static::$type); + $usedArtifact = $router->importArtifact($artifact, $appChangedFiles); + } + + if (!$usedArtifact) { + $this->read_dir_file(APP_PATH . 'routes'); + } + + if (!$routeChanged && !empty($appChangedFiles) && ($scanConfig['cache_enabled'] ?? false)) { + $scanner->scanFiles($appChangedFiles, APP_PATH . 'app/', null, !$usedArtifact); + } elseif (!$usedArtifact) { + $scanner->scan(APP_PATH . 'app/'); + } else { + $scanner->scanFiles([], APP_PATH . 'app/', null, false); + } $this->reset($container); + $artifactState->store(static::$type, $container->get(DataGrip::class)->get(static::$type)->exportArtifact()); $coordinator->done(); } @@ -208,19 +252,8 @@ class Router public function reset(ContainerInterface $container): void { $router = $container->get(DataGrip::class)->get(static::$type); - foreach ($router->getMethods() as $name => $method) { - $middlewares = $method->getMiddlewares(); - - foreach ($middlewares as $key => $middleware) { - $middlewares[$key] = di($middleware); - } - - $requestHandler = new HttpRequestHandler($middlewares, $method); - $validator = MiddlewareManager::getValidator($method->getClass(), $method->getMethod()); - if (!is_null($validator)) { - $requestHandler->withValidatorMiddleware(new ValidatorMiddleware(di(ResponseInterface::class), $method->getClass(), $method->getMethod())); - } - $router->setHttpHandler($name, $requestHandler); + if ((bool)config('servers.reload.scan.prebuild_http_handlers', false)) { + $router->warmHttpHandlers(); } } @@ -253,10 +286,19 @@ class Router private function resolve_file($files): void { try { + static::$currentSourceFile = str_replace('\\', '/', realpath($files) ?: $files); include "$files"; } catch (\Throwable $throwable) { \Kiri::getLogger()->json_log($throwable); + } finally { + static::$currentSourceFile = null; } } + + public static function getCurrentSourceFile(): ?string + { + return static::$currentSourceFile; + } + } diff --git a/src/RouterCollector.php b/src/RouterCollector.php index 6fe86d7..56d5ab2 100644 --- a/src/RouterCollector.php +++ b/src/RouterCollector.php @@ -14,6 +14,7 @@ use Throwable; use Traversable; use Kiri\Router\Base\Middleware; use Kiri\Router\Format\ResponseFormat; +use Kiri\Router\Validator\ValidatorMiddleware; /** @@ -42,7 +43,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate /** - * @var array + * @var array */ private array $methods = []; @@ -69,7 +70,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate /** - * @return Handler[] + * @return array */ public function getMethods(): array { @@ -77,6 +78,16 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate } + public function clear(): void + { + $this->_item = []; + $this->dump = []; + $this->groupTack = []; + $this->methods = []; + $this->httpHandler = []; + } + + /** * @param string $method * @param HttpRequestHandler $handler @@ -171,14 +182,26 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate if (is_string($closure[0])) { $closure[0] = $container->get($closure[0]); } - return $interpreter->addRouteByString(... $closure); + $handler = $interpreter->addRouteByString(... $closure); + $sourceFile = Router::getCurrentSourceFile(); + if ($sourceFile !== null) { + $handler->setSourceFile($sourceFile); + $handler->setSourceKind('route_file'); + } + return $handler; } if (!str_contains($closure, '@')) { $closure .= '@'; } [$className, $method] = explode('@', $closure); $class = $container->get($this->resetName($className)); - return $interpreter->addRouteByString($class, $method); + $handler = $interpreter->addRouteByString($class, $method); + $sourceFile = Router::getCurrentSourceFile(); + if ($sourceFile !== null) { + $handler->setSourceFile($sourceFile); + $handler->setSourceKind('route_file'); + } + return $handler; } @@ -205,11 +228,99 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate */ public function register(string $path, string $method, Handler $handler): void { + if ($handler->getSourceFile() === null && $handler->getClass() !== null) { + $reflect = \Kiri::getDi()->getReflectionClass($handler->getClass()); + $handler->setSourceFile($this->normalizePath((string)$reflect->getFileName())); + $handler->setSourceKind('attribute'); + } + $this->methods[$method . '_' . $path] = $handler; $handler->setMiddlewares($this->registerMiddleware($handler->getClass(), $handler->getMethod())); } + public function exportArtifact(): array + { + $entries = []; + $hasClosureRoutes = false; + + foreach ($this->methods as $methodPath => $handler) { + if ($handler instanceof Handler && $handler->isClosure()) { + $hasClosureRoutes = true; + continue; + } + + [$requestMethod, $path] = explode('_', $methodPath, 2); + $class = $handler instanceof Handler ? $handler->getClass() : $handler->class; + $method = $handler instanceof Handler ? $handler->getMethod() : $handler->method; + $middlewares = $handler instanceof Handler ? $handler->getMiddlewares() : $handler->middlewares; + $sourceFile = $handler instanceof Handler ? $handler->getSourceFile() : $handler->sourceFile; + $sourceKind = $handler instanceof Handler ? $handler->getSourceKind() : $handler->sourceKind; + + $entries[] = [ + 'request_method' => $requestMethod, + 'path' => $path, + 'class' => $class, + 'method' => $method, + 'middlewares' => $middlewares, + 'source_file' => $sourceFile, + 'source_kind' => $sourceKind, + ]; + } + + return [ + 'has_closure_routes' => $hasClosureRoutes, + 'entries' => $entries, + ]; + } + + + public function importArtifact(array $artifact, array $excludeSourceFiles = []): bool + { + if (($artifact['has_closure_routes'] ?? false) === true) { + return false; + } + + $entries = $artifact['entries'] ?? null; + if (!is_array($entries)) { + return false; + } + + $exclude = array_fill_keys(array_map([$this, 'normalizePath'], $excludeSourceFiles), true); + foreach ($entries as $entry) { + if (!is_array($entry)) { + continue; + } + + $sourceFile = $entry['source_file'] ?? null; + if (is_string($sourceFile) && isset($exclude[$this->normalizePath($sourceFile)])) { + continue; + } + + $class = $entry['class'] ?? null; + $method = $entry['method'] ?? null; + $requestMethod = $entry['request_method'] ?? null; + $path = $entry['path'] ?? null; + + if (!is_string($class) || !is_string($method) || !is_string($requestMethod) || !is_string($path)) { + 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', + ); + } + + return true; + } + + /** * @param string $class * @param string $method @@ -289,7 +400,12 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate */ public function query(string $path, string $method): HttpRequestHandler { - return $this->httpHandler[$method . '_' . $path] ?? $this->not_found_handler(); + $key = $method . '_' . $path; + if (!isset($this->httpHandler[$key]) && isset($this->methods[$key])) { + $this->httpHandler[$key] = $this->compileHandler($this->methods[$key]); + } + + return $this->httpHandler[$key] ?? $this->not_found_handler(); } @@ -309,6 +425,14 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate } + public function warmHttpHandlers(): void + { + foreach ($this->methods as $name => $method) { + $this->httpHandler[$name] = $this->compileHandler($method); + } + } + + /** * @param string $route * @return string @@ -324,6 +448,40 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate } + private function normalizePath(string $path): string + { + $resolved = realpath($path) ?: $path; + return str_replace('\\', '/', $resolved); + } + + + private function compileHandler(Handler|RouteEntry $method): HttpRequestHandler + { + if ($method instanceof RouteEntry) { + $controller = \Kiri::getDi()->get($method->class); + $handler = di(ControllerInterpreter::class)->addRouteByString($controller, $method->method); + $handler->setRequestMethod($method->requestMethod); + $handler->setMiddlewares($method->middlewares); + $handler->setSourceFile($method->sourceFile); + $handler->setSourceKind($method->sourceKind); + $method = $handler; + } + + $middlewares = $method->getMiddlewares(); + foreach ($middlewares as $key => $middleware) { + $middlewares[$key] = di($middleware); + } + + $requestHandler = new HttpRequestHandler($middlewares, $method); + $validator = Middleware::getValidator($method->getClass(), $method->getMethod()); + if ($validator !== null) { + $requestHandler->withValidatorMiddleware(new ValidatorMiddleware(di(\Psr\Http\Message\ResponseInterface::class), $method->getClass(), $method->getMethod())); + } + + return $requestHandler; + } + + /** * @param mixed $offset * @return bool