|
@@ -1,390 +0,0 @@
|
|
|
-<?php
|
|
|
-
|
|
|
-namespace Objects {
|
|
|
-
|
|
|
- use Driver\Logger\Logger;
|
|
|
- use Objects\Router\AbstractRoute;
|
|
|
-
|
|
|
- class Router {
|
|
|
-
|
|
|
- private User $user;
|
|
|
- private Logger $logger;
|
|
|
- protected array $routes;
|
|
|
- protected array $statusCodeRoutes;
|
|
|
-
|
|
|
- public function __construct(User $user) {
|
|
|
- $this->user = $user;
|
|
|
- $this->logger = new Logger("Router", $user->getSQL());
|
|
|
- $this->routes = [];
|
|
|
- $this->statusCodeRoutes = [];
|
|
|
- }
|
|
|
-
|
|
|
- public function run(string $url): string {
|
|
|
-
|
|
|
- // TODO: do we want a global try cache and return status page 500 on any error?
|
|
|
- // or do we want to have a global status page function here?
|
|
|
-
|
|
|
- $url = strtok($url, "?");
|
|
|
- foreach ($this->routes as $route) {
|
|
|
- $pathParams = $route->match($url);
|
|
|
- if ($pathParams !== false) {
|
|
|
- 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 \Api\Template\Render($this->user);
|
|
|
- $res = $req->execute(["file" => "error_document.twig", "parameters" => $params]);
|
|
|
- if ($res) {
|
|
|
- return $req->getResult()["html"];
|
|
|
- } else {
|
|
|
- var_dump($req->getLastError());
|
|
|
- $description = htmlspecialchars($params["status_description"]);
|
|
|
- return "<b>$code - $description</b>";
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public function addRoute(AbstractRoute $route) {
|
|
|
- if (preg_match("/^\d+$/", $route->getPattern())) {
|
|
|
- $this->statusCodeRoutes[$route->getPattern()] = $route;
|
|
|
- } else {
|
|
|
- $this->routes[] = $route;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public function writeCache(string $file): bool {
|
|
|
-
|
|
|
- $routes = "";
|
|
|
- foreach ($this->routes as $route) {
|
|
|
- $constructor = $route->generateCache();
|
|
|
- $routes .= "\n \$this->addRoute($constructor);";
|
|
|
- }
|
|
|
-
|
|
|
- $date = (new \DateTime())->format("Y/m/d H:i:s");
|
|
|
- $code = "<?php
|
|
|
-
|
|
|
-/**
|
|
|
- * DO NOT EDIT!
|
|
|
- * This file is automatically generated by the RoutesAPI on $date.
|
|
|
- */
|
|
|
-
|
|
|
-namespace Cache;
|
|
|
-use Objects\User;
|
|
|
-use Objects\Router;
|
|
|
-
|
|
|
-class RouterCache extends Router {
|
|
|
-
|
|
|
- public function __construct(User \$user) {
|
|
|
- parent::__construct(\$user);$routes
|
|
|
- }
|
|
|
-}
|
|
|
-";
|
|
|
-
|
|
|
- if (@file_put_contents($file, $code) === false) {
|
|
|
- $this->logger->severe("Could not write Router cache file: $file");
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- public function getUser(): User {
|
|
|
- return $this->user;
|
|
|
- }
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-namespace Objects\Router {
|
|
|
-
|
|
|
- use Api\Parameter\Parameter;
|
|
|
- use Elements\Document;
|
|
|
- use Objects\Router;
|
|
|
- use PHPUnit\TextUI\ReflectionException;
|
|
|
-
|
|
|
- abstract class AbstractRoute {
|
|
|
-
|
|
|
- private string $pattern;
|
|
|
- private bool $exact;
|
|
|
-
|
|
|
- public function __construct(string $pattern, bool $exact = true) {
|
|
|
- $this->pattern = $pattern;
|
|
|
- $this->exact = $exact;
|
|
|
- }
|
|
|
-
|
|
|
- private static function parseParamType(?string $type): ?int {
|
|
|
- if ($type === null || trim($type) === "") {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- $type = strtolower(trim($type));
|
|
|
- if (in_array($type, ["int", "integer"])) {
|
|
|
- return Parameter::TYPE_INT;
|
|
|
- } else if (in_array($type, ["float", "double"])) {
|
|
|
- return Parameter::TYPE_FLOAT;
|
|
|
- } else if (in_array($type, ["bool", "boolean"])) {
|
|
|
- return Parameter::TYPE_BOOLEAN;
|
|
|
- } else {
|
|
|
- return Parameter::TYPE_STRING;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public function getPattern(): string {
|
|
|
- return $this->pattern;
|
|
|
- }
|
|
|
-
|
|
|
- public abstract function call(Router $router, array $params): string;
|
|
|
-
|
|
|
- protected function getArgs(): array {
|
|
|
- return [$this->pattern, $this->exact];
|
|
|
- }
|
|
|
-
|
|
|
- public function generateCache(): string {
|
|
|
- $reflection = new \ReflectionClass($this);
|
|
|
- $className = $reflection->getName();
|
|
|
- $args = implode(", ", array_map(function ($arg) {
|
|
|
- return var_export($arg, true);
|
|
|
- }, $this->getArgs()));
|
|
|
- return "new \\$className($args)";
|
|
|
- }
|
|
|
-
|
|
|
- public function match(string $url) {
|
|
|
-
|
|
|
- # /test/{abc}/{param:?}/{xyz:int}/{aaa:int?}
|
|
|
- $patternParts = explode("/", Router::cleanURL($this->pattern, false));
|
|
|
- $countPattern = count($patternParts);
|
|
|
- $patternOffset = 0;
|
|
|
-
|
|
|
- # /test/param/optional/123
|
|
|
- $urlParts = explode("/", $url);
|
|
|
- $countUrl = count($urlParts);
|
|
|
- $urlOffset = 0;
|
|
|
-
|
|
|
- $params = [];
|
|
|
- for (; $patternOffset < $countPattern; $patternOffset++) {
|
|
|
-
|
|
|
- if (!preg_match("/^{.*}$/", $patternParts[$patternOffset])) {
|
|
|
-
|
|
|
- // not a parameter? check if it matches
|
|
|
- if ($urlOffset >= $countUrl || $urlParts[$urlOffset] !== $patternParts[$patternOffset]) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- $urlOffset++;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // we got a parameter here
|
|
|
- $paramDefinition = explode(":", substr($patternParts[$patternOffset], 1, -1));
|
|
|
- $paramName = array_shift($paramDefinition);
|
|
|
- $paramType = array_shift($paramDefinition);
|
|
|
- $paramOptional = endsWith($paramType, "?");
|
|
|
- if ($paramOptional) {
|
|
|
- $paramType = substr($paramType, 0, -1);
|
|
|
- }
|
|
|
-
|
|
|
- $paramType = self::parseParamType($paramType);
|
|
|
- if ($urlOffset >= $countUrl || $urlParts[$urlOffset] === "") {
|
|
|
- if ($paramOptional) {
|
|
|
- $param = $urlParts[$urlOffset] ?? null;
|
|
|
- if ($param !== null && $paramType !== null && Parameter::parseType($param) !== $paramType) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- $params[$paramName] = $param;
|
|
|
- if ($urlOffset < $countUrl) {
|
|
|
- $urlOffset++;
|
|
|
- }
|
|
|
- } else {
|
|
|
- return false;
|
|
|
- }
|
|
|
- } else {
|
|
|
- $param = $urlParts[$urlOffset];
|
|
|
- if ($paramType !== null && Parameter::parseType($param) !== $paramType) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- $params[$paramName] = $param;
|
|
|
- $urlOffset++;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ($urlOffset !== $countUrl && $this->exact) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- return $params;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- class EmptyRoute extends AbstractRoute {
|
|
|
-
|
|
|
- public function __construct(string $pattern, bool $exact = true) {
|
|
|
- parent::__construct($pattern, $exact);
|
|
|
- }
|
|
|
-
|
|
|
- public function call(Router $router, array $params): string {
|
|
|
- return "";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- class StaticFileRoute extends AbstractRoute {
|
|
|
-
|
|
|
- private string $path;
|
|
|
- private int $code;
|
|
|
-
|
|
|
- public function __construct(string $pattern, bool $exact, string $path, int $code = 200) {
|
|
|
- parent::__construct($pattern, $exact);
|
|
|
- $this->path = $path;
|
|
|
- $this->code = $code;
|
|
|
- }
|
|
|
-
|
|
|
- public function call(Router $router, array $params): string {
|
|
|
- http_response_code($this->code);
|
|
|
- return serveStatic(WEBROOT, $this->path);
|
|
|
- }
|
|
|
-
|
|
|
- protected function getArgs(): array {
|
|
|
- return array_merge(parent::getArgs(), [$this->path, $this->code]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- class StaticRoute extends AbstractRoute {
|
|
|
-
|
|
|
- private string $data;
|
|
|
- private int $code;
|
|
|
-
|
|
|
- public function __construct(string $pattern, bool $exact, string $data, int $code = 200) {
|
|
|
- parent::__construct($pattern, $exact);
|
|
|
- $this->data = $data;
|
|
|
- $this->code = $code;
|
|
|
- }
|
|
|
-
|
|
|
- public function call(Router $router, array $params): string {
|
|
|
- http_response_code($this->code);
|
|
|
- return $this->data;
|
|
|
- }
|
|
|
-
|
|
|
- protected function getArgs(): array {
|
|
|
- return array_merge(parent::getArgs(), [$this->data, $this->code]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- class RedirectRoute extends AbstractRoute {
|
|
|
-
|
|
|
- private string $destination;
|
|
|
- private int $code;
|
|
|
-
|
|
|
- public function __construct(string $pattern, bool $exact, string $destination, int $code = 307) {
|
|
|
- parent::__construct($pattern, $exact);
|
|
|
- $this->destination = $destination;
|
|
|
- $this->code = $code;
|
|
|
- }
|
|
|
-
|
|
|
- public function call(Router $router, array $params): string {
|
|
|
- header("Location: $this->destination");
|
|
|
- http_response_code($this->code);
|
|
|
- return "";
|
|
|
- }
|
|
|
-
|
|
|
- protected function getArgs(): array {
|
|
|
- return array_merge(parent::getArgs(), [$this->destination, $this->code]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- class DocumentRoute extends AbstractRoute {
|
|
|
-
|
|
|
- private string $className;
|
|
|
- private array $args;
|
|
|
- private ?\ReflectionClass $reflectionClass;
|
|
|
-
|
|
|
- public function __construct(string $pattern, bool $exact, string $className, ...$args) {
|
|
|
- parent::__construct($pattern, $exact);
|
|
|
- $this->className = $className;
|
|
|
- $this->args = $args;
|
|
|
- $this->reflectionClass = null;
|
|
|
- }
|
|
|
-
|
|
|
- private function loadClass(): bool {
|
|
|
-
|
|
|
- if ($this->reflectionClass === null) {
|
|
|
- try {
|
|
|
- $file = getClassPath($this->className);
|
|
|
- if (file_exists($file)) {
|
|
|
- $this->reflectionClass = new \ReflectionClass($this->className);
|
|
|
- if ($this->reflectionClass->isSubclassOf(Document::class)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (ReflectionException $exception) {
|
|
|
- $this->reflectionClass = null;
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- $this->reflectionClass = null;
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- public function match(string $url) {
|
|
|
- $match = parent::match($url);
|
|
|
- if ($match === false || !$this->loadClass()) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- return $match;
|
|
|
- }
|
|
|
-
|
|
|
- protected function getArgs(): array {
|
|
|
- return array_merge(parent::getArgs(), [$this->className], $this->args);
|
|
|
- }
|
|
|
-
|
|
|
- public function call(Router $router, array $params): string {
|
|
|
- if (!$this->loadClass()) {
|
|
|
- return $router->returnStatusCode(500, [ "message" => "Error loading class: $this->className"]);
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- $args = array_merge([$router->getUser()], $this->args);
|
|
|
- $document = $this->reflectionClass->newInstanceArgs($args);
|
|
|
- return $document->getCode($params);
|
|
|
- } catch (\ReflectionException $e) {
|
|
|
- return $router->returnStatusCode(500, [ "message" => "Error loading class $this->className: " . $e->getMessage()]);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|