From bff1953f9e9ad9d87e7765c21b3751deb117d9bf Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 10 Nov 2025 20:52:23 +0400 Subject: [PATCH] =?UTF-8?q?d=C3=A9but=20travail=20sur=20renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/web/base/BasicPage.php | 10 ++ src/web/base/Html5Renderer.php | 26 +++ src/web/base/RouteManager.php | 285 +++++++++++++++++++++++++++++++++ src/web/base/TRenderer.php | 11 ++ src/web/model/IPage.php | 6 + src/web/model/IRenderer.php | 6 + src/web/page.php | 26 +++ src/web/route.php | 27 ++++ 8 files changed, 397 insertions(+) create mode 100644 src/web/base/BasicPage.php create mode 100644 src/web/base/Html5Renderer.php create mode 100644 src/web/base/RouteManager.php create mode 100644 src/web/base/TRenderer.php create mode 100644 src/web/model/IPage.php create mode 100644 src/web/model/IRenderer.php create mode 100644 src/web/page.php create mode 100644 src/web/route.php diff --git a/src/web/base/BasicPage.php b/src/web/base/BasicPage.php new file mode 100644 index 0000000..a5a45ce --- /dev/null +++ b/src/web/base/BasicPage.php @@ -0,0 +1,10 @@ +render(func::with(route::get_error($e))->invoke()); + } + } + } +} diff --git a/src/web/base/RouteManager.php b/src/web/base/RouteManager.php new file mode 100644 index 0000000..5981668 --- /dev/null +++ b/src/web/base/RouteManager.php @@ -0,0 +1,285 @@ +eroutes = []; + $this->proutes = []; + $this->errorPage = null; #XXX + if ($routes !== null) $this->addRoute(...$routes); + } + + /** routes pour les chemins exacts. elles ont la priorité sur les préfixes */ + protected array $eroutes; + + /** routes pour les préfixes de chemin */ + protected array $proutes; + + private static function get_package(?string $class): ?string { + if ($class === null) return null; + str::del_prefix($class, "\\"); + if (($pos = strrpos($class, "\\")) === false) return ""; + else return substr($class, 0, $pos); + } + + function addRoute(array ...$routes): void { + $eroutes =& $this->eroutes; + $proutes =& $this->proutes; + foreach ($routes as $route) { + $path = cv::not_null($route["path"] ?? null, "route path"); + str::del_prefix($path, "/"); + $page = cv::not_null($route["page"] ?? null, "route page"); + $package = self::get_package($page); + $args = cl::with($route["args"] ?? null); + $mode = vint::with($route["mode"] ?? null); + $aliases = cl::with($route["aliases"] ?? null); + $route = [ + "path" => $path, + "page" => $page, + "package" => $package, + "args" => $args, + "mode" => $mode, + "aliases" => $aliases, + ]; + switch ($mode) { + case self::MODE_EXACT: + # route exacte + $eroutes[$path] = $route; + break; + case self::MODE_PREFIX: + case self::MODE_PACKAGE: + case self::MODE_PACKAGE2: + if ($path && !str::ends_with("/", $path)) { + # chemin ne se terminant pas par '/': faire aussi la correspondance + # exacte + $eroutes[$path] = $route; + } + $proutes[$page] = $route; + break; + default: + throw exceptions::invalid_value($mode, "route mode"); + } + # faire les aliases + foreach ($route["aliases"] as $alias) { + str::del_prefix($alias, "/"); + $route["path"] = $alias; + $route["mode"] = self::MODE_EXACT; + $eroutes[$alias] = $route; + } + } + } + + protected string $errorPage; + + /** + * spécifier la page par défaut si aucune route ne correspond + */ + function setErrorPage(string $page): void { + $this->errorPage = $page; + } + + function getErrorPage($error): array { + return [$this->errorPage, false, $error]; + } + + /** + * Calculer $page à partir de $path avec les règles suivantes: + * - supprimer le suffixe '.php' + * - remplacer '/' et '--' par '\\' + * - pour chaque élément, transformer camel-case en CamelCase + * - rajouter le suffixe Page + * + * voici comment est calculée la valeur de départ: + * - si $path se termine par '.php', prendre le chemin SANS le suffixe '.php' + * - si $path est la forme '*.php/SUFFIX', prendre SUFFIX comme valeur de + * départ + */ + private function computePackagePage(?string $path, string $package): string { + str::del_prefix($path, "/"); + if (str::ends_with(".php", $path)) { + str::del_suffix($path, ".php"); + } elseif (($pos = strpos($path, ".php/")) !== false) { + $path = substr($path, $pos + 5); + } + $path = str_replace("--", "/", $path); + + $parts = explode("/", "$path-page"); + $last = count($parts) - 1; + for ($i = 0; $i <= $last; $i++) { + $parts[$i] = str::us2camel($parts[$i]); + if ($i == $last) $parts[$i] = str::upper1($parts[$i]); + } + str::add_suffix($package, "\\"); + return $package.implode("\\", $parts); + } + + /** + * obtenir une class dérivée de {@link IPage} correspondant au chemin spécifié + * e.g '/index.php' + * + * si $path===null, prendre le chemin courant i.e "$php_self.$path_info" + * + * Retourner `[$class, false, ...$args]` qui est directement utilisable par + * {@link func}. Le classe doit être instanciée par l'utilisateur + */ + function getPage($path=null): array { + if ($path === null) $path = $_SERVER["PHP_SELF"]; + str::del_prefix($path, "/"); + + # essayer d'abord des routes exactes + $route = cl::get($this->eroutes, $path); + if ($route !== null) { + return [$route["page"], false, ...$route["args"]]; + } + # puis, essayer dans l'ordre les routes basées sur des préfixes + foreach ($this->proutes as $route) { + switch ($route["mode"]) { + case self::MODE_PREFIX: + $prefix = $route["path"]; + if ($prefix) str::add_suffix($prefix, "/"); + if (str::starts_with($prefix, $path)) { + return [$route["page"], false, ...$route["args"]]; + } + break; + } + } + # chemin non trouvé, essayer de calculer à partir de $path + $page = false; + foreach ($this->proutes as $route) { + switch ($route["mode"]) { + case self::MODE_PACKAGE: + case self::MODE_PACKAGE2: + $prefix = $route["path"]; + if ($prefix) str::add_suffix($prefix, "/"); + if (str::starts_with($prefix, $path)) { + $page = $this->computePackagePage( + str::without_prefix($prefix, $path), + $route["package"]); + if (class_exists($page)) return [$page, false, ...$route["args"]]; + } + break; + } + } + # sinon prendre la page par défaut (qui est en fait la page d'erreur) + $errorMessage = "$path: unable to find page class"; + if ($page !== false) $errorMessage .= " $page"; + return $this->getErrorPage($errorMessage); + } + + /** + * Calculer $path à partir de $page avec les règles suivantes: + * - supprimer le suffixe Page + * - pour chaque élément, transformer CamelCase en camel-case + * - remplacer '\\' par '/' + * - rajouter le suffixe '.php' + * + * NB: cette fonction n'est pas la symétrique de {@link computePackagePage()}. + * par exemple: + * - computePackagePage() transforme 'p--do-it.php' en 'p\\DoItPage' + * - computePackagePath() transforme 'p\\DoItPage' en 'p/do-it.php' + * pour les cas particulier, il vaut donc mieux faire des routes exactes + */ + private function computePackagePath(string $prefix, string $page, string $package, string $sep="/"): string { + # préfixe + str::add_suffix($prefix, "/"); + # classe + if ($package) str::del_prefix($page, "$package\\"); + str::del_suffix($page, "Page"); + $parts = explode("\\", $page); + $last = count($parts) - 1; + for ($i = 0; $i <= $last; $i++) { + if ($i == $last) $parts[$i] = str::lower1($parts[$i]); + $parts[$i] = str::camel2us($parts[$i], false, "-"); + } + $path = implode($sep, $parts); + return "$prefix$path.php"; + } + + /** + * obtenir le chemin correspondant à la page spécifiée. + * + * @param string|IPage $page + */ + function getPath($page): string { + if ($page instanceof IPage) $page = get_class($page); + if (!is_string($page)) { + throw exceptions::invalid_type($page, "page", ["string", IPage::class]); + } + # d'abord les chemins exacts + foreach ($this->eroutes as $route) { + if ($page === $route["page"]) return $route["path"]; + } + # puis les préfixes + foreach ($this->proutes as $route) { + switch ($route["mode"]) { + case self::MODE_PREFIX: + if ($page === $route["page"]) return $route["path"]; + break; + } + } + # puis les packages + foreach ($this->proutes as $route) { + $mode = $route["mode"]; + switch ($mode) { + case self::MODE_PACKAGE: + case self::MODE_PACKAGE2: + $package = $route["package"]; + if (str::starts_with($package, $page)) { + if ($mode == self::MODE_PACKAGE) $sep = "/"; + else $sep = "--"; + return $this->computePackagePath($route["path"], $page, $package, $sep); + } + break; + } + } + # pas trouvé + throw exceptions::invalid_value($page, "page", "chemin non trouvé"); + } +} diff --git a/src/web/base/TRenderer.php b/src/web/base/TRenderer.php new file mode 100644 index 0000000..59b2a18 --- /dev/null +++ b/src/web/base/TRenderer.php @@ -0,0 +1,11 @@ +invoke(); + else throw exceptions::invalid_type($page, "page", ["string", "array"]); + } + /** @var IPage $page */ + $page->RENDERER()->render($page); + } +} diff --git a/src/web/route.php b/src/web/route.php new file mode 100644 index 0000000..be247b0 --- /dev/null +++ b/src/web/route.php @@ -0,0 +1,27 @@ +addRoute(...$routes); } + + static final function set_error($page): void { self::$route->setErrorPage($page); } + static final function get_error($error): array { return self::$route->getErrorPage($error); } + + static final function get_page(?string $path=null): array { return self::$route->getPage($path); } + static final function get_path($page): string { return self::$route->getPath($page); } +} + +new class extends route { + function __construct() { + self::$route = new RouteManager(); + } +};