Codeskill

Learn to code, step by step

Building a simple router (front controller)

A simple router and front controller. Instead of about.php, contact.php, and twenty copies of the same header, every request goes through public/index.php, which picks a handler based on the URL.

URL rewriting

Apache needs .htaccess in public/ to send requests to index.php (nginx uses try_files). Example for Apache:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

Parse the request path

$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';

// Optional: strip a base path if the app lives in a subdirectory
$uri = rtrim($uri, '/') ?: '/';

Route table

<?php
declare(strict_types=1);

namespace App;

class Router
{
    private array $routes = [];

    public function get(string $path, callable $handler): void
    {
        $this->routes['GET'][$path] = $handler;
    }

    public function post(string $path, callable $handler): void
    {
        $this->routes['POST'][$path] = $handler;
    }

    public function dispatch(string $method, string $uri): void
    {
        $handler = $this->routes[$method][$uri] ?? null;

        if (!$handler) {
            http_response_code(404);
            echo 'Not found';
            return;
        }

        $handler();
    }
}

Wire it in index.php

$router = new AppRouter();

$router->get('/', fn () => (new AppControllersHomeController())->index());
$router->get('/notes', fn () => (new AppControllersNoteController())->index());
$router->post('/notes', fn () => (new AppControllersNoteController())->store());

$router->dispatch($method, $uri);

Route parameters (simple approach)

For /notes/5, match with a regex or split the path:

if (preg_match('#^/notes/(d+)$#', $uri, $m)) {
    $id = (int) $m[1];
    (new AppControllersNoteController())->show($id);
    return;
}

Framework routers do fancier matching. For learning, explicit paths and one regex for IDs is enough. Keep the router readable.

404 and method not allowed

Return proper status codes. A missing page is 404. POST to a GET-only route can be 405. Browsers and APIs care about this.

The front controller pattern scales from a five-page site to most frameworks under the hood. You are building the same idea, stripped down.

PreviousTalking to MySQL safely – prepared statements everywhere