From f2164e9af19ce921f319d7fa4c7758520648897d Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 8 May 2024 10:30:46 +0100 Subject: [PATCH] feature: improve default routing, closes PhpGt/Routing#61 --- router.default.php | 147 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 23 deletions(-) diff --git a/router.default.php b/router.default.php index 0bbcc860..c0d585e7 100644 --- a/router.default.php +++ b/router.default.php @@ -12,38 +12,77 @@ use Gt\Routing\Path\DynamicPath; use Gt\WebEngine\View\BaseView; use Gt\WebEngine\View\HTMLView; +use Gt\WebEngine\View\NullView; class DefaultRouter extends BaseRouter { #[Any(name: "page-route", accept: "text/html,application/xhtml+xml")] public function page(Request $request):void { $pathMatcher = new PathMatcher("page"); $this->setViewClass(HTMLView::class); - $pathMatcher->addFilter(function(string $filePath, string $uriPath, string $baseDir):bool { -// There are three types of matching files: Basic, Magic and Dynamic. -// Basic is where a URI matches directly to a file on disk. -// Magic is where a URI matches a PHP.Gt-specific file, like _common or _header. -// Dynamic is where a URI matches a file/directory marked as dynamic with "@". - $basicFileMatch = new BasicFileMatch($filePath, $baseDir); - if($basicFileMatch->matches($uriPath)) { - return true; + $this->pathMatcherFilter($pathMatcher); + +// This sort function allow multiple headers and footers to be in nested +// directories, so the highest level header is at the start of the list, +// with the reverse logic applied to footers. +// TODO: Extract into own function. Should this be maintained within PHP.Gt/Routing ? + $headerFooterSort = function(string $a, string $b):int { + $fileNameA = pathinfo($a, PATHINFO_FILENAME); + $fileNameB = pathinfo($b, PATHINFO_FILENAME); + + if($fileNameA === "_header") { + if($fileNameB === "_header") { + $aDepth = substr_count($a, "/"); + $bDepth = substr_count($b, "/"); + if($aDepth > $bDepth) { + return 1; + } + elseif($aDepth < $bDepth) { + return -1; + } + else { + return 0; + } + } + + + return -1; } - $magicFileMatch = new MagicFileMatch($filePath, $baseDir); - if($magicFileMatch->matches($uriPath)) { - return true; + if($fileNameA === "_footer") { + if($fileNameB === "_footer") { + $aDepth = substr_count($a, "/"); + $bDepth = substr_count($b, "/"); + if($aDepth < $bDepth) { + return 1; + } + elseif($aDepth > $bDepth) { + return -1; + } + else { + return 0; + } + } + + return 1; } - return false; - }); - // TODO: add logic and view assembly in the api directory - // (configured from $this->routerConfig) + if($fileNameB === "_header") { + return 1; + } + + if($fileNameB === "_footer") { + return -1; + } + + return 0; + }; $sortNestLevelCallback = fn(string $a, string $b) => - substr_count($a, "/") > substr_count($b, "/"); - $headerSort = fn(string $a, string $b) => - strtok(basename($a), ".") === "_header" ? -1 : 1; - $footerSort = fn(string $a, string $b) => - strtok(basename($a), ".") === "_footer" ? 1 : -1; + substr_count($a, "/") > substr_count($b, "/") + ? 1 + : (substr_count($a, "/") < substr_count($b, "/") + ? -1 + : 0); $matchingLogics = $pathMatcher->findForUriPath( $request->getUri()->getPath(), @@ -60,12 +99,74 @@ public function page(Request $request):void { "page", "html" ); - usort($matchingViews, $sortNestLevelCallback); - usort($matchingViews, $headerSort); - usort($matchingViews, $footerSort); + usort($matchingViews, $headerFooterSort); + foreach($matchingViews as $path) { + $this->addToViewAssembly($path); + } + } + + #[Any(name: "api-route", accept: "application/xml,application/json")] + public function api( + Request $request + ):void { + $pathMatcher = new PathMatcher("api"); + $this->pathMatcherFilter($pathMatcher); + $this->setViewClass(NullView::class); + $sortNestLevelCallback = fn(string $a, string $b) => + substr_count($a, "/") > substr_count($b, "/") + ? 1 + : (substr_count($a, "/") < substr_count($b, "/") + ? -1 + : 0); + $matchingLogics = $pathMatcher->findForUriPath( + $request->getUri()->getPath(), + "api", + "php" + ); + usort($matchingLogics, $sortNestLevelCallback); + foreach($matchingLogics as $path) { + $this->addToLogicAssembly($path); + } + + $matchingViews = $pathMatcher->findForUriPath( + $request->getUri()->getPath(), + "api", + "xml" + ); foreach($matchingViews as $path) { $this->addToViewAssembly($path); } } + + public function pathMatcherFilter(PathMatcher $pathMatcher):void { + $pathMatcher->addFilter(function(string $filePath, string $uriPath, string $baseDir):bool { + foreach(glob($baseDir . $uriPath . ".*") as $globMatch) { + $URI_CONTAINER = pathinfo($uriPath, PATHINFO_DIRNAME); + $TRIM_THIS = $baseDir . $URI_CONTAINER; + if(str_starts_with($globMatch, $TRIM_THIS)) { + $trimmed = substr($filePath, strlen($TRIM_THIS)); + if(str_contains($trimmed, "@")) { + return false; + } + } + } + +// There are three types of matching files: Basic, Magic and Dynamic. +// Basic is where a URI matches directly to a file on disk. +// Magic is where a URI matches a PHP.Gt-specific file, like _common or _header. +// Dynamic is where a URI matches a file/directory marked as dynamic with "@". + $basicFileMatch = new BasicFileMatch($filePath, $baseDir); + if($basicFileMatch->matches($uriPath)) { + return true; + } + + $magicFileMatch = new MagicFileMatch($filePath, $baseDir); + if($magicFileMatch->matches($uriPath)) { + return true; + } + + return false; + }); + } }