Piszemy klasę router. Idziemy dalej w kierunku stworzenia własnego frameworka MVC w języku PHP. Do dzieła.
Na razie nasz projekt wygląda tak – w folderze htdocs mam folder o nazwie sometry. Dobrą praktyką jest nie zaśmiecać sobie htdocs plikami bez folderu, jeżeli na localhoście robimy więcej niż jeden projekt na raz.
W sometry mamy plik .htaccess przekierowujący każde uri na plik entry.php:
RewriteEngine On
RewriteRule ^ entry.php
W entry.php mamy taką oto namiastkę routera:
<?php
class Helper {
public static function request_uri()
{
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = explode("/", $path);
$action = array_pop($path);
$controller = array_pop($path);
$filepath = __DIR__ . "/{$controller}_{$action}.php";
if(file_exists($filepath)){
require $filepath;
}
else {
die("Wrong URI");
}
}
}
//http://localhost/sometry/home/index
Helper::request_uri();
//from home_index.php
Sprawia ona, że URI (bez query stringa) kończące się na „/home/index” mapowane jest na plik „home_index.php” zaś „/product/list” na plik „product_list.php” i tak dalej.
Oczywiste są ograniczenia tego rozwiązania – nie możemy np. mapować „/” na nasz „home_index.php”. Nie możemy mapować „/product” na „product_list.php”.
W ogóle nie możemy mieć dwóch ścieżek do jednego kontrolera. Pora to naprawić.
Piszemy klasę router (w entry.php):
<?php
class Router
{
const SUBFOLDER = "/sometry";
private array $routes = [];
}
Pamiętamy, że jesteśmy w podfolderze. No chyba, że nie jesteśmy i piszemy wszystko w htdocs.
Tak czy inaczej, chcemy mieć możliwość podawania routów i parametrów jak poniżej:
$router = new Router;
$router->add("/home/index", ["controller" => "home", "action" => "index"]);
$router->add("/products", ["controller" => "products", "action" => "index"]);
$router->add("/", ["controller" => "home", "action" => "index"]);
$params = $router->match($path);
Potrzebujemy zatem metod add i match. Metoda add pozwala nam dodać route oraz zmapowane parametry kontroler i akcja:
class Router
{
const SUBFOLDER = "/sometry";
private array $routes = [];
public function add(string $path, array $params): void
{
$this->routes[] = [
"path" => $path,
"params" => $params
];
}
}
Metoda match z kolei ma poradzić sobie z porównywaniem przekazanej ścieżki (pozyskanej z request uri) z ścieżką przez nas podaną (plus bierzemy pod uwagę podfolder):
class Router
{
const SUBFOLDER = "/sometry";
private array $routes = [];
public function add(string $path, array $params): void
{
$this->routes[] = [
"path" => $path,
"params" => $params
];
}
public function match(string $path): array|bool
{
foreach ($this->routes as $route) {
if (self::SUBFOLDER . $route["path"] === $path) {
return $route["params"];
}
}
return false;
}
}
Ewentualnie możemy olać podfolder i dopisywać go za każdym razem do add – nasz wybór.
Pora teraz dopisać koniec do naszego entry.php:
<?php
class Router
{
//(...)
}
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$router = new Router;
$router->add("/home/index", ["controller" => "home", "action" => "index"]);
$router->add("/products", ["controller" => "product", "action" => "list"]);
$router->add("/product/list", ["controller" => "product", "action" => "list"]);
$router->add("/", ["controller" => "home", "action" => "index"]);
$params = $router->match($path);
if ($params === false) {
exit("No route matched");
}
$action = $params["action"];
$controller = $params["controller"];
require __DIR__ . "/{$controller}_{$action}.php";
exit;
Jak widać możemy mapować wiele URI do jednego pliku. Z podfolderem też problemu nie mamy.
Cóż, następnym razem postaramy się, aby mieć klasy kontrolera z prawdziwego zdarzenia, gdzie $controller to nazwa klasy, zaś $action to nazwa metody.