addRoute('GET', $path, $handler, $middleware); } /** * Define a POST route */ public function post(string $path, $handler, array $middleware = []): void { $this->addRoute('POST', $path, $handler, $middleware); } /** * Define a PUT route */ public function put(string $path, $handler, array $middleware = []): void { $this->addRoute('PUT', $path, $handler, $middleware); } /** * Define a DELETE route */ public function delete(string $path, $handler, array $middleware = []): void { $this->addRoute('DELETE', $path, $handler, $middleware); } /** * Add global middleware applied to all routes */ public function use($middleware): void { $this->globalMiddleware[] = $middleware; } private function addRoute(string $method, string $path, $handler, array $middleware): void { // Convert path matching expressions: /api/tickets/{id} -> regex $pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<$1>[^/]+)', $path); $pattern = '#^' . $pattern . '$#'; $this->routes[] = [ 'method' => $method, 'path' => $path, 'pattern' => $pattern, 'handler' => $handler, 'middleware' => $middleware ]; } /** * Match current request and execute middleware and controller action */ public function dispatch(Request $request, Response $response): void { $method = $request->getMethod(); $path = $request->getPath(); // Handle CORS Preflight Preemptively if ($method === 'OPTIONS') { $allowedOrigin = getenv('ALLOWED_ORIGIN') ?: '*'; $response->setHeader('Access-Control-Allow-Origin', $allowedOrigin); $response->setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); $response->setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With'); $response->setHeader('Vary', 'Origin'); $response->setStatusCode(200); exit; } foreach ($this->routes as $route) { if ($route['method'] === $method && preg_match($route['pattern'], $path, $matches)) { // Filter named captures from regex match $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY); // Run global middleware first foreach ($this->globalMiddleware as $mw) { $mwInstance = new $mw(); $mwInstance->handle($request, $response); } // Run route specific middleware foreach ($route['middleware'] as $mw) { $mwInstance = new $mw(); $mwInstance->handle($request, $response); } // Execute Controller $handler = $route['handler']; if (is_array($handler) && count($handler) === 2) { list($controllerClass, $action) = $handler; if (class_exists($controllerClass)) { $controller = new $controllerClass(); if (method_exists($controller, $action)) { // Call action with Request, Response and URI dynamic parameters call_user_func_array([$controller, $action], array_merge([$request, $response], $params)); return; } } } elseif (is_callable($handler)) { call_user_func_array($handler, array_merge([$request, $response], $params)); return; } error_log("Handler error for route: [{$method}] {$path}"); $response->error("Internal Server Error", 500); return; } } // Route not found error_log("Route not found: [{$method}] {$path}"); $response->error("Not Found", 404); } }