<?php /* * Copyright (c) 2022. Micha Espey <tracer@24unix.net> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. * */ namespace App\Service; use App\Entity\Route; use Closure; /* * A small router implementation for the address book demo. * Currently it doesn't handle GET requests, as not needed. * But if I reuse the code in my bindApi I'll support GET as well. */ class Router { /* * The easiest wy to differentiate between static and dynamic routes is using * two arrays, no need to pollute the class Route with that information */ private array $staticRoutes; private array $dynamicRoutes; public function __construct(private readonly Template $template) { // empty body } /* * This method takes a route like /admin/users/{user} and creates a regex to match on call * More complex routes as /posts/{thread}/show/{page} are supported as well. */ function addRoute(string $name, string $route, Closure $callback): void { // check for parameters preg_match_all(pattern: "/(?<={).+?(?=})/", subject: $route, matches: $matches); $parameters = $matches[0]; // create regex for route: $regex = preg_replace(pattern: '/(?<={).+?(?=})/', replacement: '(.*?)', subject: $route); // code below is ugly, better match including the braces $regex = str_replace(search: '{', replace: '', subject: $regex); $regex = str_replace(search: '}', replace: '', subject: $regex); $regex = '/^' . str_replace(search: "/", replace: '\\/', subject: $regex) . '$/i'; $route = new Route(name: $name, route: $route, regEx: $regex, parameters: $parameters, callback: $callback); if ($parameters) { $this->dynamicRoutes[] = $route; } else { $this->staticRoutes[] = $route; } } /* * Check if there is a known route and executes the callback. */ public function handleRouting(): void { $requestUri = $_SERVER['REQUEST_URI']; /* * Static routes have preference over dynamic ones, so * /admin/user/add to add and * /admin/user/{name} to edit is possible. */ foreach ($this->staticRoutes as $route) { if (preg_match(pattern: $route->getRegex(), subject: $requestUri, matches: $matches)) { call_user_func(callback: $route->getCallback()); return; } } foreach ($this->dynamicRoutes as $route) { if (preg_match(pattern: $route->getRegex(), subject: $requestUri, matches: $matches)) { $parameters = []; foreach ($route->getParameters() as $id => $parameter) { $parameters[$parameter] = $matches[$id + 1]; } // PHP is mad about named parameters in call_user_func // Uncaught Error: Unknown named parameter $args in … // But PHPStorm seems happy without them. So what? call_user_func($route->getCallback(), $parameters); return; } } $this->template->render(templateName: 'status/404.html.php'); } }