web php
Routing Basics
Routing decides which code handles an HTTP request. A route usually matches a request method and a path, then calls a controller, handler, or page script.
For example, GET /invoices might show a list, while POST /invoices might create a new invoice.
Match method and path
<?php
declare(strict_types=1);
/**
* @param array<string, string> $routes
*/
function dispatch(array $routes, string $method, string $path): string
{
$key = $method . ' ' . $path;
return $routes[$key] ?? '404 Not Found';
}
$routes = [
'GET /invoices' => 'InvoiceListController',
'POST /invoices' => 'CreateInvoiceController',
];
echo dispatch($routes, 'GET', '/invoices') . PHP_EOL;
echo dispatch($routes, 'DELETE', '/invoices') . PHP_EOL;
// Prints:
// InvoiceListController
// 404 Not Found
Real routers are more powerful, but the idea is the same: match the request and choose the handler.
Route parameters
Routes often include variables, such as /invoices/123.
<?php
declare(strict_types=1);
function invoiceIdFromPath(string $path): ?int
{
if (preg_match('#^/invoices/([1-9][0-9]*)$#', $path, $matches) !== 1) {
return null;
}
return (int) $matches[1];
}
echo invoiceIdFromPath('/invoices/123') ?? 'missing';
echo PHP_EOL;
echo invoiceIdFromPath('/invoices/abc') ?? 'missing';
// Prints:
// 123
// missing
Route parameters are still input. Validate them before using them in database queries or authorization checks.
404 and 405 are different
404 Not Found means no route matches that path.
405 Method Not Allowed means the path exists, but not for that HTTP method.
<?php
declare(strict_types=1);
/**
* @param list<array{method: string, path: string}> $routes
*/
function routeStatus(array $routes, string $method, string $path): int
{
$pathExists = false;
foreach ($routes as $route) {
if ($route['path'] !== $path) {
continue;
}
$pathExists = true;
if ($route['method'] === $method) {
return 200;
}
}
return $pathExists ? 405 : 404;
}
$routes = [
['method' => 'GET', 'path' => '/invoices'],
['method' => 'POST', 'path' => '/invoices'],
];
echo routeStatus($routes, 'DELETE', '/invoices') . PHP_EOL;
echo routeStatus($routes, 'GET', '/missing') . PHP_EOL;
// Prints:
// 405
// 404
Frameworks often handle this for you. Knowing the distinction helps when debugging forms that submit to the wrong method or URL.
Keep route definitions small
Routes should point to work; they should not contain all the work.
Good route definitions are easy to scan:
<?php
declare(strict_types=1);
$routes = [
'GET /' => 'HomeController',
'GET /login' => 'ShowLoginController',
'POST /login' => 'LoginController',
'POST /logout' => 'LogoutController',
];
echo count($routes) . ' routes registered' . PHP_EOL;
// Prints:
// 4 routes registered
Controllers can then handle validation, authorization, service calls, rendering, and redirects.
Route order can matter
Some routers match the first route that fits. If a broad route appears before a specific route, it can steal requests.
For example, /posts/{slug} could match /posts/archive unless /posts/archive is defined first or constrained properly.
What to check in a project
Check that state-changing actions use appropriate methods such as POST, PATCH, or DELETE rather than GET.
Check route parameters are constrained where possible. IDs should not accept arbitrary strings unless the application expects slugs.
Check 404 and 405 behaviour when adding or changing routes.
Check that route files remain readable. Large anonymous functions inside route files can make the application hard to maintain.
Check named routes or URL helpers where the framework provides them, so links do not break when paths change.
What you should be able to do
After this lesson, you should be able to explain routing as method-and-path matching, extract route parameters safely, distinguish 404 from 405, and keep route definitions separate from heavy request handling.
Practice
Task: Build A Tiny Router
Write a small PHP script that matches requests to handlers.
Requirements
- Use
declare(strict_types=1);. - Match routes by HTTP method and path.
- Include at least one GET route and one POST route.
- Return a handler name for a matched route.
- Return
404for an unknown path. - Return
405when the path exists but the method is wrong. - Print all three outcomes.
Check Your Work
Run the script and confirm the matched, missing, and wrong-method cases are different.
Show solution
This solution keeps the route table simple and makes the 404/405 distinction explicit.
<?php
declare(strict_types=1);
/**
* @param list<array{method: string, path: string, handler: string}> $routes
*/
function matchRoute(array $routes, string $method, string $path): string
{
$pathExists = false;
foreach ($routes as $route) {
if ($route['path'] !== $path) {
continue;
}
$pathExists = true;
if ($route['method'] === $method) {
return $route['handler'];
}
}
return $pathExists ? '405 Method Not Allowed' : '404 Not Found';
}
$routes = [
['method' => 'GET', 'path' => '/invoices', 'handler' => 'InvoiceListController'],
['method' => 'POST', 'path' => '/invoices', 'handler' => 'CreateInvoiceController'],
];
echo matchRoute($routes, 'GET', '/invoices') . PHP_EOL;
echo matchRoute($routes, 'DELETE', '/invoices') . PHP_EOL;
echo matchRoute($routes, 'GET', '/missing') . PHP_EOL;
// Prints:
// InvoiceListController
// 405 Method Not Allowed
// 404 Not Found
Why This Works
The normal case proves method and path matching. The wrong-method case proves the path exists but the method is not allowed. The missing path proves the router can return a true 404.