Tworzymy już pełnoprawną klasę routera z metodami dispatch oraz add i wcześniej napisaną normalizePath. Do dzieła.
Oto zabawy z lekcji poprzedniej:
<?php
$path = '/transaction/{transaction}/receipt/{receipt}/';
$regexPath = preg_replace('#{[^/]+}#', '([^/]+)', $path);
echo $path . "<br>";
///transaction/{transaction}/receipt/{receipt}/
echo $regexPath ."<br>";
///transaction/([^/]+)/receipt/([^/]+)/
var_dump(preg_match("#^{$regexPath}$#", '/transaction/123/receipt/helloworld/', $paramValues));
//int(1)
array_shift($paramValues);
print_r($paramValues);
// Array (
// [0] => 123
// [1] => helloworld )
preg_match_all('#{([^/]+)}#', $path, $paramKeys);
$paramKeys = $paramKeys[1];
print_r($paramKeys);
// Array (
// [0] => transaction
// [1] => receipt )
$params = array_combine($paramKeys, $paramValues);
print_r($params);
// Array (
// [transaction] => 123
// [receipt] => helloworld
// )
A oto prosty pseudo-kontroler:
class TransactionController {
public function makeTransaction($transaction, $receipt){
echo "Making transaction {$transaction} <br>";
echo "Recepit: {$receipt} <br>";
echo "DONE";
}
}
$transactionController = new TransactionController();
$transactionController->makeTransaction(...$params);
//Making transaction 123
//Recepit: helloworld
//DONE
Piszemy klasę routera z metodą normalizePath z lekcji poprzedniej, ale już nie statyczną:
class Router {
private array $routes = [];
public function normalizePath($path){
$path = trim($path, '/');
$path = "/{$path}/";
$path = preg_replace('#[/]{2,}#', '/', $path);
return $path;
}
}
Musimy móc jakoś dodawać te nasze routy:
class Router {
private array $routes = [];
//(...)
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
];
}
}
Teraz metoda dispatch – kontenerem się na razie nie przejmujemy:
class Router {
private array $routes = [];
//(...)
public function dispatch(string $path, string $method, $container = null)
{
$path = $this->normalizePath($path);
$method = strtoupper($_POST['_METHOD'] ?? $method);
}
}
Zrobiliśmy obsługę metod spoofing, czyli jeżeli mamy _METHOD to nadpisuje to metodę przekazaną do dispatch.
Teraz pokażmy jak router będzie używany – zobrazowanie tego pomoże nam napisać resztę metody dispatch:
$router = new Router;
$router->add('GET', '/transaction/{transaction}/receipt/{receipt}/', [TransactionController::class, 'makeTransaction']);
$router->dispatch('/transaction/123/receipt/helloworld/', 'GET');
Ok, po uporządkowaniu wszystkiego mamy taki kod:
<?php
class TransactionController {
public function makeTransaction($transaction, $receipt){
echo "Making transaction {$transaction} <br>";
echo "Recepit: {$receipt} <br>";
echo "DONE";
}
}
class Router {
private array $routes = [];
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;
}
}
}
$router = new Router;
$router->add('GET', '/transaction/{transaction}/receipt/{receipt}/', [TransactionController::class, 'makeTransaction']);
$router->dispatch('/transaction/123/receipt/helloworld/', 'GET');
// Making transaction 123
// Recepit: helloworld
// DONE
Jedyne nowe, co tu widzimy to ten fragment:
//(...)
[$class, $function] = $route['controller'];
$controllerInstance = $container ?
$container->resolve($class) :
new $class;
$action = fn () => $controllerInstance->{$function}(...$params);
$action();
//(...)
Czyli dekompozycja tablicy, na nazwę klasy i metody (trzeci argument metody add), sprawdzenie czy mamy kontener (nie mamy) i stworzenie klasy kontrolera (nie mylić z kontenerem – to coś, do czego musimy jeszcze dojść).
Dalej tworzymy dynamicznie metodę i przekazujemy jej params. Powiem tak – nie robimy tego najlepiej.
Nie powinno być tamtego spreada, ale to wina kontrolera. Poprawmy:
class TransactionController {
public function makeTransaction($params){
echo "Making transaction {$params['transaction']} <br>";
echo "Recepit: {$params['receipt']} <br>";
echo "DONE";
}
}
Teraz nasz dispatch może wyglądać tak:
class Router {
private array $routes = [];
//(...)
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;
}
}
}
Użycie metod bez zmian:
$router = new Router;
$router->add('GET', '/transaction/{transaction}/receipt/{receipt}/', [TransactionController::class, 'makeTransaction']);
$router->dispatch('/transaction/123/receipt/helloworld/', 'GET');
// Making transaction 123
// Recepit: helloworld
// DONE
W przyszłości poznamy też inne patenty na router (niekoniecznie lepsze, ale inne) plus ten jeszcze rozbudujemy o middleware i kontener oraz pewien automatyzm, zarówno w zaciąganiu routes jak i dispatchu, który będzie z URI swoje argumenty pobierał.
Na razie przeanalizujmy dogłębnie to, co już mamy.