diff --git a/src/Router.php b/src/Router.php index 04fba0a..aa5927c 100644 --- a/src/Router.php +++ b/src/Router.php @@ -312,9 +312,28 @@ class Router ); $scanner->setConfig($scanConfig); + // 从 manifest 获取 Master 扫描过的类 $manifestEntries = $scanner->getManifestClasses(); - if (empty($manifestEntries)) { - \Kiri::getLogger()->warning('Annotation route rebuild: manifest is empty, annotation routes will NOT be registered'); + $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; } @@ -323,84 +342,93 @@ class Router $errorCount = 0; $dispatchCount = 0; - foreach ($manifestEntries as $path => $entry) { - $classes = is_array($entry) && isset($entry['classes']) ? $entry['classes'] : []; - foreach ($classes as $class) { - if (!class_exists($class)) { + // 只处理用户命名空间下的类,排除框架和 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; } - $classCount++; - try { - $reflect = $container->getReflectionClass($class); - if (!$reflect->isInstantiable() || $reflect->isTrait() || $reflect->isEnum() || $reflect->isInterface()) { + foreach ($reflect->getMethods() as $method) { + if ($method->isStatic() || $method->getDeclaringClass()->getName() !== $class) { continue; } - foreach ($reflect->getMethods() as $method) { - if ($method->isStatic() || $method->getDeclaringClass()->getName() !== $class) { + foreach ($method->getAttributes() as $attribute) { + $attrName = $attribute->getName(); + if (!class_exists($attrName)) { 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()}"); + 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()}"); } + } 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} classes processed, {$dispatchCount} annotation routes dispatched, {$routeCount} total routes, {$errorCount} errors"); + \Kiri::getLogger()->info("Annotation route rebuild: {$classCount} user classes processed, {$dispatchCount} annotation routes dispatched, {$routeCount} total routes, {$errorCount} errors"); - // 搜索特定路径的诊断日志,方便排查 404 问题 + // 搜索特定路径的诊断日志 $searchPaths = ['/headers']; foreach ($searchPaths as $searchPath) { $found = []; - foreach ($manifestEntries as $path => $entry) { - $classes = is_array($entry) && isset($entry['classes']) ? $entry['classes'] : []; - foreach ($classes as $class) { - 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')) { - $found[] = "{$class}::{$method->getName()} -> {$attr->getName()} path={$routePath} version={$instance->version}"; - } + 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) {} - } + } + } 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 of {$classCount} classes"); + \Kiri::getLogger()->warning("Annotation route search '{$searchPath}': NO annotation found in any class"); } } }