container = $container; } public function register($method, $path, $handler) { $this->routes[$method][$path] = $handler; } public function get($path, $handler) { $this->register('GET', $path, $handler); } public function post($path, $handler) { $this->register('POST', $path, $handler); } private function normalizePath(string $path): string { $path = '/' . trim($path, '/'); $path = preg_replace('#^/api(?=/|$)#i', '', $path); $path = '/' . trim($path, '/'); return $path === '' ? '/' : $path; } public function dispatch(Request $req, Response $res) { $method = $req->method(); $path = $this->normalizePath($req->path()); // extract version prefix /v1/... if (preg_match('#^/v([0-9]+)(/.*)?$#', $path, $m)) { $version = $m[1]; $pathWithoutVersion = $m[2] ?? '/'; } else { $version = '1'; $pathWithoutVersion = $path; } // try exact route with versioned namespace $routeKey = $pathWithoutVersion; if (!isset($this->routes[$method][$routeKey])) { return $res->json(['error' => 'Not found'], 404); } $handler = $this->routes[$method][$routeKey]; // handler can be 'UsersController@index' or 'Controllers\\UsersController@index' if (strpos($handler, '@') !== false) { list($class, $function) = explode('@', $handler); // if class not namespaced, prefix with Api\\Controllers\\V{n}\\ if (strpos($class, '\\') === false) { $class = "Api\\Controllers\\V{$version}\\" . $class; } } else { return $res->json(['error' => 'Invalid handler'], 500); } if (!class_exists($class)) { return $res->json(['error' => 'Controller not found'], 500); } $controller = $this->container->make($class); return $controller->$function($req, $res); } }