context = $context; $this->routes = []; $this->statusCodeRoutes = []; $this->activeRoute = null; $this->requestedUri = null; $this->context->router = $this; $sql = $context->getSQL(); if ($sql) { $this->addRoute(new ApiRoute()); $this->logger = new Logger("Router", $sql); } else { $this->logger = new Logger("Router"); } } public function getActiveRoute(): ?Route { return $this->activeRoute; } public function getRequestedUri(): ?string { return $this->requestedUri; } public function run(string $url): string { // TODO: do we want a global try cache and return status page 500 on any error? $this->requestedUri = $url; $url = strtok($url, "?"); foreach ($this->routes as $route) { $pathParams = $route->match($url); if ($pathParams !== false) { $this->activeRoute = $route; return $route->call($this, $pathParams); } } return $this->returnStatusCode(404); } public function returnStatusCode(int $code, array $params = []): string { http_response_code($code); $params["status_code"] = $code; $params["status_description"] = HTTP_STATUS_DESCRIPTIONS[$code] ?? "Unknown Error"; $route = $this->statusCodeRoutes[strval($code)] ?? null; if ($route) { return $route->call($this, $params); } else { $req = new \Core\API\Template\Render($this->context); $res = $req->execute(["file" => "error_document.twig", "parameters" => $params]); if ($res) { return $req->getResult()["html"]; } else { $description = htmlspecialchars($params["status_description"]); return "$code - $description"; } } } public function addRoute(Route $route) { if (preg_match("/^\/(\d+)$/", $route->getPattern(), $re)) { $this->statusCodeRoutes[$re[1]] = $route; } $this->routes[] = $route; } public function writeCache(string $file): bool { $routes = ""; $uses = [ "Core\Objects\Context", "Core\Objects\Router\Router", ]; foreach ($this->routes as $route) { // do not generate cache for static api route if ($route instanceof ApiRoute) { continue; } $class = $route->getClass(); $constructor = $route->generateCache(); $uses[] = $class->getName(); $routes .= "\n \$this->addRoute($constructor);"; } $uses = implode("\n", array_map(function ($use) { return "use $use;"; }, array_unique($uses))); $date = (new \DateTime())->format("Y/m/d H:i:s"); $code = "logger->severe("Could not create directory: $directory"); return false; } } if (@file_put_contents($file, $code) === false) { $this->logger->severe("Could not write Router cache file: $file"); return false; } return true; } public function getContext(): Context { return $this->context; } public function getLogger(): Logger { return $this->logger; } public static function cleanURL(string $url, bool $cleanGET = true): string { // strip GET parameters if ($cleanGET) { if (($index = strpos($url, "?")) !== false) { $url = substr($url, 0, $index); } } // strip document reference part if (($index = strpos($url, "#")) !== false) { $url = substr($url, 0, $index); } // strip leading slash return preg_replace("/^\/+/", "", $url); } public function getRoutes(bool $includeStatusRoutes = false): array { if (!$includeStatusRoutes && !empty($this->statusCodeRoutes)) { return array_filter($this->routes, function ($route) { return !in_array($route, $this->statusCodeRoutes); }); } return $this->routes; } }