groupPrefix, '/'); $prefix = $prefix === '/' ? '' : $prefix; $trimmedPath = '/' . trim($path, '/'); $trimmedPath = $trimmedPath === '/' ? '' : $trimmedPath; $fullPath = $prefix . $trimmedPath; $fullPath = $fullPath === '' ? '/' : $fullPath; $this->routes[strtoupper($method)][] = [ 'path' => $fullPath, 'callback' => $callback, 'middleware' => array_merge($this->groupMiddleware, $middleware) ]; } public function get(string $path, array|callable $callback, array $middleware = []): void { $this->addRoute('GET', $path, $callback, $middleware); } public function post(string $path, array|callable $callback, array $middleware = []): void { $this->addRoute('POST', $path, $callback, $middleware); } public function put(string $path, array|callable $callback, array $middleware = []): void { $this->addRoute('PUT', $path, $callback, $middleware); } public function delete(string $path, array|callable $callback, array $middleware = []): void { $this->addRoute('DELETE', $path, $callback, $middleware); } /** * Group routes under attributes like prefix and middleware. */ public function group(array $attributes, callable $callback): void { $previousPrefix = $this->groupPrefix; $previousMiddleware = $this->groupMiddleware; if (isset($attributes['prefix'])) { $this->groupPrefix = $previousPrefix . '/' . trim($attributes['prefix'], '/'); } if (isset($attributes['middleware'])) { $this->groupMiddleware = array_merge($previousMiddleware, (array)$attributes['middleware']); } $callback($this); $this->groupPrefix = $previousPrefix; $this->groupMiddleware = $previousMiddleware; } /** * Match path against configured routes. */ public function resolve(Request $request): array { $method = $request->getMethod(); $path = '/' . trim($request->getPath(), '/'); $routes = $this->routes[$method] ?? []; foreach ($routes as $route) { $pattern = $this->compilePattern($route['path']); if (preg_match($pattern, $path, $matches)) { // Filter out non-string keys from named capture groups $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY); return [ 'callback' => $route['callback'], 'middleware' => $route['middleware'], 'params' => $params ]; } } throw new Exception("Route not found", 404); } /** * Convert route notation {param} into regex capture groups. */ private function compilePattern(string $path): string { $cleanPath = '/' . trim($path, '/'); $cleanPath = $cleanPath === '/' ? '' : $cleanPath; $pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<$1>[^/]+)', $cleanPath); return '#^' . ($pattern === '' ? '/' : $pattern) . '$#'; } }