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.