Compare commits

..

5 Commits

3 changed files with 148 additions and 60 deletions

100
src/Entity/Route.php Normal file
View File

@ -0,0 +1,100 @@
<?php
namespace App\Entity;
use Closure;
class Route
{
public function __construct(
private string $name,
private string $route,
private string $regEx,
private array $parameters,
private Closure $callback
)
{
// empty body
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* @return string
*/
public function getRoute(): string
{
return $this->route;
}
/**
* @param string $route
*/
public function setRoute(string $route): void
{
$this->route = $route;
}
/**
* @return string
*/
public function getRegEx(): string
{
return $this->regEx;
}
/**
* @param string $regEx
*/
public function setRegEx(string $regEx): void
{
$this->regEx = $regEx;
}
/**
* @return array
*/
public function getParameters(): array
{
return $this->parameters;
}
/**
* @param array $parameters
*/
public function setParameters(array $parameters): void
{
$this->parameters = $parameters;
}
/**
* @return Closure
*/
public function getCallback(): Closure
{
return $this->callback;
}
/**
* @param Closure $callback
*/
public function setCallback(Closure $callback): void
{
$this->callback = $callback;
}
}

View File

@ -3,51 +3,61 @@
namespace App\Service; 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 bind Api I'll enable GET as well.
*/
class Router class Router
{ {
private array $uri;
private array $routes; private array $routes;
public function __construct() /*
* This method takes a route like /admin/users/{user} and creates a regex to match on call
*/
function addRoute(string $name, string $route, Closure $callback): void
{ {
$uri = parse_url(url: $_SERVER['REQUEST_URI'], component: PHP_URL_PATH); // check for parameters
$this->uri = explode(separator: '/', string: $uri); preg_match_all(pattern: "/(?<={).+?(?=})/", subject: $route, matches: $matches);
} $parameters = $matches[0];
public function registerRoute(string $route, \Closure $callback): void // create regex for route:
{ $regex = preg_replace(pattern: '/(?<={).+?(?=})/', replacement: '(.*?)', subject: $route);
$this->routes[$route] = $callback; // code below is ugly, better match including the braces
} $regex = str_replace(search: '{', replace: '', subject: $regex);
$regex = str_replace(search: '}', replace: '', subject: $regex);
private function matchRoute($url = '/users/tracer/posts/tracer', $method = 'GET') $regex = '/^' . str_replace(search: "/", replace: '\\/', subject: $regex) . '$/i';
{ $route = new Route(name: $name, route: $route, regEx: $regex, parameters: $parameters, callback: $callback);
$reqUrl = $url;
$this->routes[] = $route;
$reqUrl = rtrim(string: $reqUrl, characters: "/");
foreach ($this->routes as $route => $closure) {
// convert urls like '/users/:uid/posts/:pid' to regular expression
// $pattern = "@^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['url'])) . "$@D";
$pattern = "@^" . preg_replace('\\/users/:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', $route) . "$@D";
// echo $pattern."\n";
$params = [];
// check if the current request params the expression
$match = preg_match($pattern, $reqUrl, $params);
if ($match) {
// remove the first match
array_shift($params);
// call the callback with the matched positions as params
// return call_user_func_array($route['callback'], $params);
return [$route, $params];
}
}
return [];
} }
/*
* Check is there is a known route and executed the callback.
* Currently no 404 handling.
*/
public function handleRouting(): void public function handleRouting(): void
{ {
$foo = $this->matchRoute(); $requestUri = $_SERVER['REQUEST_URI'];
var_dump($foo);
foreach ($this->routes 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;
}
}
// Throw a 404 result later …
die("Invalid route: $requestUri");
} }
} }

View File

@ -11,30 +11,8 @@ class Template
// empty body // empty body
} }
/**
* @throws Exception
*/
public function render(string $templateName): void public function render(string $templateName): void
{ {
$template = file_get_contents(filename: $this->templateDir . $templateName); include $this->templateDir . $templateName;
// search for includes
preg_match_all(pattern: '/{% include ?\'?(.*?)\'? ?%}/i', subject: $template, matches: $matches, flags: PREG_SET_ORDER);
foreach ($matches as $value) {
$token = $value[0];
$include = $this->templateDir . $value[1];
if (file_exists(filename: $include)) {
$replacement = file_get_contents(filename: $include);
} else {
throw new Exception(message: "Missing included file: $include");
}
$template = str_replace(search: $token, replace: $replacement, subject: $template);
}
// remove the original template code
$template = preg_replace(pattern: '/{% include ?\'?(.*?)\'? ?%}/i', replacement: '', subject: $template);
echo $template;
} }
} }