Rozbudowa routera – ma obsługiwać cały łańcuch różnych middlewares w swojej metodzie dispatch. Do dzieła!
Nasz projekt na razie:
<?php
class TransactionController {
public function makeTransaction($params){
echo "Making transaction {$params['transaction']} <br>";
echo "Recepit: {$params['receipt']} <br>";
echo "DONE";
}
}
class Router {
private array $routes = [];
private array $middlewares = [];
public function normalizePath($path){
$path = trim($path, '/');
$path = "/{$path}/";
$path = preg_replace('#[/]{2,}#', '/', $path);
return $path;
}
public function add(string $method, string $path, array $controller)
{
$path = $this->normalizePath($path);
$regexPath = preg_replace('#{[^/]+}#', '([^/]+)', $path);
$this->routes[] = [
'path' => $path,
'method' => strtoupper($method),
'controller' => $controller,
'middlewares' => [],
'regexPath' => $regexPath
];
}
public function dispatch(string $path, string $method, $container = null)
{
$path = $this->normalizePath($path);
$method = strtoupper($_POST['_METHOD'] ?? $method);
foreach ($this->routes as $route) {
if (
!preg_match("#^{$route['regexPath']}$#", $path, $paramValues) ||
$route['method'] !== $method
) {
continue;
}
array_shift($paramValues);
preg_match_all('#{([^/]+)}#', $route['path'], $paramKeys);
$paramKeys = $paramKeys[1];
$params = array_combine($paramKeys, $paramValues);
[$class, $function] = $route['controller'];
$controllerInstance = $container ?
$container->resolve($class) :
new $class;
$action = fn () => $controllerInstance->{$function}($params);
$action();
return;
}
}
public function addMiddleware(string $middleware)
{
$this->middlewares[] = $middleware;
}
public function addRouteMiddleware(string $middleware)
{
$lastRouteKey = array_key_last($this->routes);
$this->routes[$lastRouteKey]['middlewares'][] = $middleware;
}
}
interface MiddlewareInterface
{
public function process(callable $next);
}
class StupidMiddleware implements MiddlewareInterface
{
public function process(callable $next)
{
echo "This is stupid middleware speaking on all routes <br>";
$next();
}
}
class StupidRouteMiddleware implements MiddlewareInterface
{
public function process(callable $next)
{
echo "This is stupid route middleware speakin on this route! <br>";
$next();
}
}
Teraz mocny rzut oka na metodę dispatch:
//(...)
public function dispatch(string $path, string $method, $container = null)
{
$path = $this->normalizePath($path);
$method = strtoupper($_POST['_METHOD'] ?? $method);
foreach ($this->routes as $route) {
if (
!preg_match("#^{$route['regexPath']}$#", $path, $paramValues) ||
$route['method'] !== $method
) {
continue;
}
array_shift($paramValues);
preg_match_all('#{([^/]+)}#', $route['path'], $paramKeys);
$paramKeys = $paramKeys[1];
$params = array_combine($paramKeys, $paramValues);
[$class, $function] = $route['controller'];
$controllerInstance = $container ?
$container->resolve($class) :
new $class;
$action = fn () => $controllerInstance->{$function}($params);
//TUTAJ WSTAWIMY MIDDLEWARE
$action();
return;
}
}
//(...)
Swoją drogą zwróćmy uwagę na strukturę naszej pętli:
foreach ($this->routes as $route) {
if (
!preg_match("#^{$route['regexPath']}$#", $path, $paramValues) ||
$route['method'] !== $method
) {
continue;
}
//ROBI SWOJE
return;
}
Idzie po ścieżkach, jak coś nie pasuje – continue. Jak pasuje to robi swoje a potem return wyrywający nas z pętli. Z tej pętli pochodzi też zmienna $route.
Ok, najpierw połączmy middleware danego route i middleware globalny (dla wszystkich ścieżek):
foreach ($this->routes as $route) {
if (
!preg_match("#^{$route['regexPath']}$#", $path, $paramValues) ||
$route['method'] !== $method
) {
continue;
}
array_shift($paramValues);
preg_match_all('#{([^/]+)}#', $route['path'], $paramKeys);
$paramKeys = $paramKeys[1];
$params = array_combine($paramKeys, $paramValues);
[$class, $function] = $route['controller'];
$controllerInstance = $container ?
$container->resolve($class) :
new $class;
$action = fn () => $controllerInstance->{$function}($params);
$allMiddleware = [...$route['middlewares'], ...$this->middlewares];
$action();
return;
}
Teraz tworzymy łańcuch naszych middlewares:
public function dispatch(string $path, string $method, $container = null)
{
$path = $this->normalizePath($path);
$method = strtoupper($_POST['_METHOD'] ?? $method);
foreach ($this->routes as $route) {
if (
!preg_match("#^{$route['regexPath']}$#", $path, $paramValues) ||
$route['method'] !== $method
) {
continue;
}
array_shift($paramValues);
preg_match_all('#{([^/]+)}#', $route['path'], $paramKeys);
$paramKeys = $paramKeys[1];
$params = array_combine($paramKeys, $paramValues);
[$class, $function] = $route['controller'];
$controllerInstance = $container ?
$container->resolve($class) :
new $class;
$action = fn () => $controllerInstance->{$function}($params);
$allMiddleware = [...$route['middlewares'], ...$this->middlewares];
foreach ($allMiddleware as $middleware) {
$middlewareInstance = $container ?
$container->resolve($middleware) :
new $middleware;
$action = fn () => $middlewareInstance->process($action);
}
$action();
return;
}
}
Kontenerem się nie przejmujemy – na to przyjdzie czas. Ok, sprawdźmy jak to działa:
<?php
class TransactionController {
//(...)
}
class Router {
//(...)
}
interface MiddlewareInterface
{
public function process(callable $next);
}
class StupidMiddleware implements MiddlewareInterface
{
public function process(callable $next)
{
echo "This is stupid middleware speaking on all routes <br>";
$next();
}
}
class StupidRouteMiddleware implements MiddlewareInterface
{
public function process(callable $next)
{
echo "This is stupid route middleware speakin on this route! <br>";
$next();
}
}
$router = new Router;
$router->addMiddleware(StupidMiddleware::class);
$router->add('GET', '/transaction/{transaction}/receipt/{receipt}/', [TransactionController::class, 'makeTransaction']);
$router->addRouteMiddleware(StupidRouteMiddleware::class);
$router->dispatch('/transaction/123/receipt/helloworld/', 'GET');
// This is stupid middleware speaking on all routes
// This is stupid route middleware speakin on this route!
// Making transaction 123
// Recepit: helloworld
// DONE
Ok, działa. Polecam przeanalizować jeszcze raz jak ten łańcuch wywołań jest tworzony i potem za pomocą jednego action() całość jest uruchamiana.
Rozbudowa w następnej lekcji.