request = $request; $this->currentRoute = null; //Load routes from cache, or fall back to loading from file if(!$this->loadFromCache($cacheDirPath)) { $this->loadFromRouteFile($routeDirPath); } } protected function loadFromCache(string $cachePath): bool { if(!file_exists($cachePath) || !file_exists($cachePath . DIRECTORY_SEPARATOR . 'route-cache.php')) { return false; } $this->routes = require $cachePath . DIRECTORY_SEPARATOR . 'route-cache.php'; return true; } protected function loadFromRouteFile(string $routePath): void { if(!file_exists($routePath) || !file_exists($routePath . DIRECTORY_SEPARATOR . 'routes.php')) { throw new \RuntimeException('Unable to initialize routes: route file not found.'); } $this->repository = new RouteRepository(); $routes = require $routePath . DIRECTORY_SEPARATOR . 'routes.php'; if(!is_callable($routes)) { throw new \RuntimeException('Unable to initialize routes: route file does not contain callable function.'); } //Initialize routes call_user_func($routes, $this->repository); $this->routes = $this->repository->collectRoutes(); } protected function readRequestData() { $uri = $this->request->getPathInfo(); // Remove trailing slash + enforce a slash at the start $this->requestUri = '/' . trim($uri, '/'); $this->requestMethod = $this->request->getMethod(); $this->requestHeaders = $this->request->headers; // If it's a HEAD request override it to being GET and prevent any output, as per HTTP Specification // @url http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 /*if ($_SERVER['REQUEST_METHOD'] == 'HEAD') { ob_start(); $method = 'GET'; }*/ //TODO: should this be done in application? /*$headers = [];*/ // If getallheaders() is available, use that /*if (function_exists('getallheaders')) { $headers = getallheaders(); // getallheaders() can return false if something went wrong if ($headers !== false) { $this->requestHeaders = $headers; return $headers; } } // Method getallheaders() not available or went wrong: manually extract them foreach ($_SERVER as $name => $value) { if ((substr($name, 0, 5) == 'HTTP_') || ($name == 'CONTENT_TYPE') || ($name == 'CONTENT_LENGTH')) { $headers[str_replace([' ', 'Http'], ['-', 'HTTP'], ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; } }*/ } protected function findRoute(string $path, string $method): ?Route { $method = strtoupper($method); if(key_exists($method, $this->routes)) { foreach($this->routes[$method] as $route) { if($route->match($path, $method)) { return $route; } } } return null; } protected function handleRoute(Route $route) { //Todo: run route middleware and route handler. //Maybe make middleware part of a base controller class? return $route->run(); } /** * @return mixed|bool Returns a mixed response, or a boolean false if there was no valid route. */ public function run() { //Parse request $this->readRequestData(); //Find the first matching route. Only one route will ever be executed. $route = $this->findRoute($this->requestUri, $this->requestMethod); //If a route is found, handle it and return its response. //Otherwise, return a boolean false to denote no route could be found. if($route) { $this->currentRoute = $route; return $this->handleRoute($route); } else { return false; } //If the request was a HEAD request, empty the output buffer. /*if ($_SERVER['REQUEST_METHOD'] == 'HEAD') { ob_end_clean(); }*/ //TODO: should this be done in application? } public function buildCache() { } public function getCurrentRoute(): ?Route { return $this->currentRoute; } public function getRoute(string $pattern, ?string $method): ?Route { return $this->findRoute($pattern, $method ?? 'GET'); } }