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 example
<?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 example
<?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 example
<?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 example
<?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 404 for an unknown path.
  • Return 405 when 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 example
<?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.