addRoute('GET', $path, $handler, $middlewares); } public function post(string $path, array|callable $handler, array $middlewares = []): void { $this->addRoute('POST', $path, $handler, $middlewares); } public function put(string $path, array|callable $handler, array $middlewares = []): void { $this->addRoute('PUT', $path, $handler, $middlewares); } public function delete(string $path, array|callable $handler, array $middlewares = []): void { $this->addRoute('DELETE', $path, $handler, $middlewares); } public function options(string $path, array|callable $handler): void { $this->addRoute('OPTIONS', $path, $handler, []); } private function addRoute(string $method, string $path, array|callable $handler, array $middlewares): void { $pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<$1>[^/]+)', $path); $pattern = '#^' . $pattern . '$#'; $this->routes[] = [ 'method' => $method, 'path' => $path, 'pattern' => $pattern, 'handler' => $handler, 'middlewares' => $middlewares, ]; } public function dispatch(Request $request): void { // Handle preflight CORS OPTIONS requests immediately if ($request->getMethod() === 'OPTIONS') { Response::json(['status' => 'ok']); } $method = $request->getMethod(); $path = $request->getPath(); foreach ($this->routes as $route) { if ($route['method'] !== $method) { continue; } if (preg_match($route['pattern'], $path, $matches)) { $params = []; foreach ($matches as $key => $value) { if (is_string($key)) { $params[$key] = $value; } } $this->runMiddlewares($route['middlewares'], $request, function () use ($route, $request, $params) { $this->executeHandler($route['handler'], $request, $params); }); return; } } Response::notFound("Endpoint not found: [{$method}] {$path}"); } private function runMiddlewares(array $middlewares, Request $request, callable $target): void { $pipeline = array_reduce( array_reverse($middlewares), function ($next, $middlewareClass) { return function ($req) use ($next, $middlewareClass) { $instance = new $middlewareClass(); $instance->handle($req, $next); }; }, $target ); $pipeline($request); } private function executeHandler(array|callable $handler, Request $request, array $params): void { if (is_callable($handler)) { call_user_func($handler, $request, $params); return; } [$controllerClass, $methodName] = $handler; if (!class_exists($controllerClass)) { Response::error("Controller class {$controllerClass} not found", 500); } $controller = new $controllerClass(); if (!method_exists($controller, $methodName)) { Response::error("Method {$methodName} not found on controller {$controllerClass}", 500); } $controller->$methodName($request, $params); } }