show404(); } /** * 添加路由 * @param string $method 请求方法 GET/POST 等 * @param string $path 路径,必须以 / 开头,尾部无 / * @param callable|string $callback 处理器,格式:Controller@method 或回调函数 */ public function add($method, $path, $callback) { $methods = explode('|', strtoupper($method)); // 支持多方法 $normalizedPath = $this->normalizePath($path); foreach ($methods as $m) { $this->routes[$m][$normalizedPath] = $callback; } } public function get($path, $callback) { $this->add('GET', $path, $callback); } public function post($path, $callback) { $this->add('POST', $path, $callback); } public function hasRoute($method, $path): bool { $method = strtoupper($method); $normalizedPath = $this->normalizePath($path); return isset($this->routes[$method][$normalizedPath]); } public function setDependencies(array $deps) { $this->dependencies = $deps; } /** * 路由分发 * @param string $method 请求方法 * @param string $uri 请求路径 * @param array $dependencies 依赖注入数组,键为类名,值为实例(可选) */ public function dispatch($method, $uri, $dependencies = []) { $dependencies = array_merge($this->dependencies, $dependencies); $method = strtoupper($method); $normalizedPath = $this->normalizePath($uri); if (!isset($this->routes[$method])) { header("HTTP/1.1 404 Not Found"); echo "请求方法 [$method] 无任何注册路由。"; return; } // 精确匹配 if (isset($this->routes[$method][$normalizedPath])) { $this->callHandler($this->routes[$method][$normalizedPath], [], $dependencies); return; } // 模糊匹配带参数路由 foreach ($this->routes[$method] as $routePath => $callback) { $pattern = $this->convertToRegex($routePath); if (preg_match($pattern, $normalizedPath, $matches)) { array_shift($matches); // 去掉完整匹配 $this->callHandler($callback, $matches, $dependencies); return; } } // 404 $this->handle404(); } public function group(string $prefixUri, string $controllerNamespace, array $routes) { foreach ($routes as $route) { if (count($route) < 3) { throw new \InvalidArgumentException("每个子路由必须包含 method、uri、handler"); } [$method, $subUri, $handler] = $route; // 构造完整 URI $uri = rtrim($prefixUri, '/') . '/' . ltrim($subUri, '/'); // 构造完整处理器(加命名空间) if (strpos($handler, '@') !== false) { [$controller, $action] = explode('@', $handler); $fullHandler = $controllerNamespace . '\\' . $controller . '@' . $action; } else { $fullHandler = $controllerNamespace . '\\' . $handler; } $this->add($method, $uri, $fullHandler); } } /** * 调用处理器,支持构造函数依赖注入 * @param callable|string $callback * @param array $params 传给方法的参数 * @param array $dependencies 依赖注入映射,key: 类名,value: 实例 */ private function callHandler($callback, $params = [], $dependencies = []) { $requestMethod = $_SERVER['REQUEST_METHOD'] ?? '未知请求方法'; $requestUri = $_SERVER['REQUEST_URI'] ?? '未知请求路径'; if (is_string($callback) && strpos($callback, '@') !== false) { list($class, $method) = explode('@', $callback); if (class_exists($class) && method_exists($class, $method)) { try { $reflection = new \ReflectionClass($class); $instance = null; $constructor = $reflection->getConstructor(); if ($constructor) { $ctorParams = $constructor->getParameters(); $args = []; foreach ($ctorParams as $param) { $paramType = $param->getType(); if ($paramType && !$paramType->isBuiltin()) { $paramClassName = $paramType->getName(); if (isset($dependencies[$paramClassName])) { $args[] = $dependencies[$paramClassName]; } elseif ($param->isDefaultValueAvailable()) { $args[] = $param->getDefaultValue(); } else { throw new \Exception("依赖注入失败:未提供 {$paramClassName} 实例"); } } else { $args[] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null; } } $instance = $reflection->newInstanceArgs($args); } else { $instance = new $class(); } // ⚠️ 此处加入详细异常捕获 try { call_user_func_array([$instance, $method], $params); } catch (\Throwable $e) { http_response_code(500); echo "处理错误:
"; echo "" . htmlspecialchars($e->getMessage()) . "
"; echo "文件:" . $e->getFile() . " 第 " . $e->getLine() . " 行
"; echo "
" . $e->getTraceAsString() . "
"; exit; } return; } catch (\Throwable $e) { http_response_code(500); echo "控制器初始化错误:
"; echo "" . htmlspecialchars($e->getMessage()) . "
"; echo "文件:" . $e->getFile() . " 第 " . $e->getLine() . " 行
"; echo "
" . $e->getTraceAsString() . "
"; exit; } } http_response_code(500); echo "处理错误:类 " . htmlspecialchars($class) . " 或方法 " . htmlspecialchars($method) . " 未找到。
"; echo "请求方法:{$requestMethod}
"; echo "请求路径:{$requestUri}
"; return; } if (is_callable($callback)) { try { call_user_func_array($callback, $params); } catch (\Throwable $e) { http_response_code(500); echo "回调执行错误:
"; echo "" . htmlspecialchars($e->getMessage()) . "
"; echo "文件:" . $e->getFile() . " 第 " . $e->getLine() . " 行
"; echo "
" . $e->getTraceAsString() . "
"; exit; } return; } http_response_code(500); echo "无效的路由处理器。
"; echo "请求方法:{$requestMethod}
"; echo "请求路径:{$requestUri}
"; } /** * 规范化路径,保证统一格式: * - 开头带 / * - 尾部无 / * - 根路径保持 / */ private function normalizePath($path) { $path = trim($path); if ($path === '' || $path === '/') { return '/'; } return '/' . trim($path, '/'); } /** * 将路由路径转为正则表达式,支持 {param} 动态参数 * @param string $routePath * @return string 正则表达式 */ private function convertToRegex($routePath) { $pattern = preg_replace('/\{[a-zA-Z0-9_]+\}/', '([^/]+)', $routePath); return '/^' . str_replace('/', '\/', $pattern) . '$/'; } }