Als je webapplicaties bouwt, kom je al snel twee begrippen tegen die je PHP-code structureren: middleware en routing. Samen bepalen ze hoe een binnenkomende HTTP-request door je applicatie reist en welke code er op welk moment draait. Begrijp je dit goed, dan wordt je code een stuk overzichtelijker en veiliger.
In dit artikel leer je hoe routing een URL koppelt aan de juiste handler, hoe middleware als een pipeline om je logica heen draait, en hoe je beide zelf implementeert of via een framework inzet. Alles met praktische PHP-voorbeelden die je direct kunt gebruiken.
Wat is routing in PHP?
Routing is het mechanisme dat bepaalt welke code wordt uitgevoerd op basis van de URL en HTTP-methode van een inkomende request. Wanneer een bezoeker /blog/artikel opvraagt, zoekt de router naar een bijpassende route en roept de gekoppelde controller of functie aan.
Zonder routing zou elke URL apart via een fysiek PHP-bestand bediend moeten worden. Met routing hanteer je één centraal entry-point (meestal index.php) dat alle requests afhandelt via een netjes gedefinieerde routetabel.
Waarom centrale routing?
Met één entry-point heb je volledige controle over elke request. Je kunt logging, authenticatie en error handling op één plek regelen. Bovendien krijg je mooie, leesbare URL's zonder .php-extensies.
Dit sluit nauw aan bij het MVC pattern in PHP, waar de router de request doorgeeft aan de juiste controller.
Een simpele router bouwen in PHP
Laten we een minimale router bouwen om het principe te zien. Eerst zorgen we dat alle requests via index.php lopen met een .htaccess-bestand:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
Vervolgens bouwen we de router zelf:
<?php
class Router
{
private array $routes = [];
public function add(string $method, string $path, callable $handler): void
{
$this->routes[] = [
'method' => strtoupper($method),
'path' => $path,
'handler' => $handler,
];
}
public function dispatch(string $method, string $uri): void
{
$path = parse_url($uri, PHP_URL_PATH);
foreach ($this->routes as $route) {
if ($route['method'] === $method && $route['path'] === $path) {
call_user_func($route['handler']);
return;
}
}
http_response_code(404);
echo '404, Pagina niet gevonden';
}
}
In je index.php registreer je routes:
<?php
require 'Router.php';
$router = new Router();
$router->add('GET', '/', function () {
echo 'Welkom op de homepage';
});
$router->add('GET', '/blog', function () {
echo 'Blog overzicht';
});
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
Deze router werkt, maar heeft beperkingen: geen dynamische parameters, geen middleware-ondersteuning. Dat breiden we straks uit.
Routes met parameters
Echte applicaties hebben dynamische URL's zoals /blog/mijn-artikel of /users/42. Hiervoor gebruiken we patronen. Een veelgebruikte aanpak is placeholders via een regex vertalen:
private function matchPath(string $pattern, string $path, array &$params): bool
{
$regex = preg_replace('#\{([a-z]+)\}#', '(?P<$1>[^/]+)', $pattern);
$regex = '#^' . $regex . '$#';
if (preg_match($regex, $path, $matches)) {
foreach ($matches as $key => $value) {
if (is_string($key)) {
$params[$key] = $value;
}
}
return true;
}
return false;
}
Daarmee kun je routes definiëren als /users/{id} en ontvangt je handler $id als parameter. Kennis van arrays en loops in PHP komt hier goed van pas om de route-matching netjes te structureren.
Wat is middleware?
Middleware is een laag tussen de inkomende request en je uiteindelijke route-handler. Elke middleware-component kan de request inspecteren, aanpassen, doorgeven of vroegtijdig stoppen met een eigen response.
Denk aan taken die voor meerdere routes gelden:
- Authenticatie en autorisatie controleren
- CSRF-tokens valideren
- Request-logging toevoegen
- CORS-headers instellen
- Rate limiting toepassen
- Response comprimeren
Zonder middleware zou je deze logica in elke controller herhalen. Met middleware schrijf je het één keer en gebruik je het overal.
De middleware-pipeline
Middleware werkt als een ui-model of "onion": elke laag zit om de volgende heen. De request gaat naar binnen door alle lagen tot aan de handler. Daarna reist de response in omgekeerde volgorde weer naar buiten.
Concreet: als je [LogMiddleware, AuthMiddleware, Controller] hebt, draait eerst LogMiddleware::before, dan AuthMiddleware::before, dan de controller, dan AuthMiddleware::after, en tenslotte LogMiddleware::after.
Middleware implementeren in PHP
De meest moderne aanpak volgt PSR-15, de standaard voor HTTP Server Request Handlers. Een vereenvoudigde versie zonder externe dependencies ziet er zo uit:
<?php
interface Middleware
{
public function handle(array $request, callable $next): array;
}
Elke middleware implementeert handle(). Die kan iets doen vóór $next() (richting handler) en erna (richting response).
Een authenticatie-middleware bijvoorbeeld:
class AuthMiddleware implements Middleware
{
public function handle(array $request, callable $next): array
{
if (empty($request['headers']['Authorization'])) {
return [
'status' => 401,
'body' => 'Unauthorized',
];
}
return $next($request);
}
}
Als de header ontbreekt, stopt de pipeline direct met een 401. Anders wordt $next() aangeroepen en gaat de request door naar de volgende laag.
De pipeline uitvoeren
Om middleware te koppelen aan een handler gebruik je een runner die de ketting opbouwt:
class Pipeline
{
private array $middleware = [];
public function add(Middleware $mw): self
{
$this->middleware[] = $mw;
return $this;
}
public function run(array $request, callable $handler): array
{
$next = $handler;
foreach (array_reverse($this->middleware) as $mw) {
$current = $next;
$next = fn($req) => $mw->handle($req, $current);
}
return $next($request);
}
}
Het mooie: via array_reverse en closures bouw je een geneste structuur waarin elke middleware de volgende aanroept. De kennis van functies en scope, specifiek closures, komt hier volop van pas.
Routing en middleware combineren
In een echte applicatie koppel je middleware aan specifieke routes of aan alle routes (globale middleware). Een vaak gezien patroon:
$router->add('GET', '/dashboard', [DashboardController::class, 'index'])
->middleware([AuthMiddleware::class, RateLimitMiddleware::class]);
Wanneer de router /dashboard matcht, stopt hij de handler én de route-specifieke middleware in de pipeline, samen met eventuele globale middleware. Die volledige ketting wordt uitgevoerd.
Volgorde is belangrijk
De volgorde waarin je middleware registreert bepaalt het gedrag. Logging staat bijvoorbeeld meestal bovenaan zodat álles gelogd wordt, ook authenticatie-fouten. Rate limiting staat liefst vóór authenticatie om databaseload te sparen.
Middleware en routing in frameworks
In productie bouw je dit zelden vanaf nul. Laravel heeft een uitgebreid routing-systeem met elegante middleware-syntax:
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware(['auth', 'throttle:60,1']);
Andere populaire opties:
- Slim Framework, lichtgewicht microframework met PSR-15 middleware
- Symfony, enterprise-grade framework met een krachtige HTTP Kernel
- Mezzio, volledig op PSR-15 gebouwd
Alle moderne frameworks volgen grofweg hetzelfde model: centrale router, middleware-pipeline, controllers als eindhandler.
Veelgemaakte fouten
Een paar valkuilen die je kunt vermijden:
- Business logic in middleware, houd middleware beperkt tot cross-cutting concerns. Business logic hoort in controllers of services.
- Te veel middleware stapelen, elke laag kost tijd. Gebruik route-specifieke middleware waar mogelijk, niet alles globaal.
- Vergeten de request door te geven, als je
$next($request)niet aanroept, stopt de pipeline stilletjes. - Geen database-abstractie, gebruik altijd PDO met prepared statements in je controllers, zeker als middleware user-input heeft doorgelaten.
Wanneer gebruik je middleware?
Een simpele vuistregel: als logica voor meerdere routes geldt én niet de kern van een specifieke endpoint is, past het in middleware. Voorbeelden van ideale middleware-kandidaten:
- Alle requests loggen naar een logfile
- Controleren of een gebruiker ingelogd is
- API-keys valideren
- Response-headers zoals
X-Frame-Optionstoevoegen - Requests in bepaalde omgevingen blokkeren (bijvoorbeeld staging)
Zaken die juist níet in middleware horen: complexe businessregels, data transformaties specifiek voor één route, of view rendering. Die plek is in je controller of service layer.
Testen van middleware en routes
Omdat middleware kleine, geïsoleerde stukjes code zijn, lenen ze zich uitstekend voor unit tests. Je instantieert de middleware, geeft een fake request en een fake $next-closure mee, en controleert het resultaat:
$middleware = new AuthMiddleware();
$response = $middleware->handle(
['headers' => []],
fn($req) => ['status' => 200]
);
assert($response['status'] === 401);
Voor routing-tests gebruik je meestal integration tests waarbij je een complete request door de router stuurt en de response controleert.
Veelgestelde vragen
Wat is routing in PHP?
Routing is het proces waarbij een inkomende HTTP-request op basis van URL en HTTP-methode wordt gekoppeld aan de juiste controller of functie. Een router zorgt dat /blog bij een andere handler terechtkomt dan /contact.
Wat doet middleware precies?
Middleware is code die tussen de request en de uiteindelijke handler draait. Het kan requests valideren, authenticatie controleren, logging toevoegen of de response aanpassen voordat deze de gebruiker bereikt.
Heb ik een framework nodig voor routing en middleware?
Nee, je kunt beide zelf bouwen in plain PHP. Frameworks zoals Laravel of Slim bieden echter een geteste implementatie met veel functionaliteit, wat ze voor productie vaak de betere keuze maakt.
Wat is het verschil tussen middleware en een controller?
Een controller bevat de hoofdlogica voor een specifieke route. Middleware draait eromheen en behandelt cross-cutting concerns zoals authenticatie, CORS of rate limiting die voor meerdere routes gelden.
In welke volgorde draait middleware?
Middleware draait in de volgorde waarin het is geregistreerd, als een soort ui-model. Eerst naar binnen richting de controller, daarna in omgekeerde volgorde weer naar buiten met de response.
Samengevat
Routing koppelt URL's aan handlers, middleware draait als pipeline om die handlers heen. Samen vormen ze de ruggengraat van elke moderne PHP-applicatie. Of je nu zelf bouwt of een framework gebruikt: als je het principe begrijpt, schrijf je schonere, veiligere en beter testbare code.
Wil je verder bouwen op deze basis? Verdiep je in object-oriented PHP en Composer voor dependency management, twee bouwstenen die je routing- en middleware-code professionaliseren.