web php
Middleware Concepts
Middleware is code that runs around a request handler. It can inspect the request before the controller runs, stop the request early, or modify the response on the way back out.
Frameworks use middleware for authentication, CSRF protection, rate limiting, request logging, trusted proxies, CORS, and security headers.
A request passes through layers
Middleware is often described as a pipeline:
request -> middleware -> middleware -> controller -> middleware -> middleware -> response
This small example uses functions to show the idea:
<?php
declare(strict_types=1);
/**
* @param array{user_id?: int} $request
*/
function requireLogin(array $request, callable $next): string
{
if (!isset($request['user_id'])) {
return '401 Authentication required';
}
return $next($request);
}
$controller = static fn (array $request): string => 'Dashboard for user ' . $request['user_id'];
echo requireLogin(['user_id' => 42], $controller) . PHP_EOL;
echo requireLogin([], $controller) . PHP_EOL;
// Prints:
// Dashboard for user 42
// 401 Authentication required
The middleware can choose to call $next() or return early.
Middleware can modify responses
Some middleware lets the controller run, then adds headers to the response.
<?php
declare(strict_types=1);
/**
* @param array{status: int, headers: array<string, string>, body: string} $response
* @return array{status: int, headers: array<string, string>, body: string}
*/
function addSecurityHeader(array $response): array
{
$response['headers']['X-Content-Type-Options'] = 'nosniff';
return $response;
}
$response = ['status' => 200, 'headers' => [], 'body' => 'OK'];
$response = addSecurityHeader($response);
echo $response['headers']['X-Content-Type-Options'] . PHP_EOL;
// Prints:
// nosniff
Order matters
Middleware order can change behaviour. For example:
- trusted proxy middleware adjusts scheme and client IP
- session middleware starts the session
- authentication middleware loads the user
- authorization middleware checks access
If authentication runs before the session exists, it may not find the logged-in user. If rate limiting uses the wrong client IP because trusted proxies were not handled first, it may limit the proxy instead of the real client.
Global and route middleware
Global middleware runs for most or all requests, such as logging, trusted proxies, and security headers.
Route middleware runs only for selected routes, such as auth for dashboards or admin for admin routes.
<?php
declare(strict_types=1);
$routes = [
'/dashboard' => ['middleware' => ['auth'], 'handler' => 'DashboardController'],
'/admin/users' => ['middleware' => ['auth', 'admin'], 'handler' => 'AdminUsersController'],
];
echo implode(', ', $routes['/admin/users']['middleware']) . PHP_EOL;
// Prints:
// auth, admin
What belongs in middleware
Middleware is good for cross-cutting behaviour that applies to many routes. Authentication checks, request IDs, rate limits, and security headers are good examples.
Middleware is a poor place for feature-specific business rules. If only one controller action needs the rule, keeping it near that action may be clearer.
What to check in a project
Check middleware order when debugging authentication, session, proxy, CORS, and rate-limit issues.
Check whether a route has the middleware it needs. A missing auth middleware can expose a page.
Check whether middleware returns early. A request may never reach the controller.
Check response-modifying middleware when headers appear or disappear.
What you should be able to do
After this lesson, you should be able to explain middleware as code around a request handler, identify short-circuiting middleware, recognise response-modifying middleware, and understand why middleware order matters.
Practice
Task: Build A Middleware Pipeline
Write a small PHP script that models an authentication middleware around a controller.
Requirements
- Use
declare(strict_types=1);. - Represent the request as an array.
- Create middleware that requires
user_id. - If
user_idis missing, return a401message. - If
user_idexists, call the next handler. - Include one authenticated case and one unauthenticated case.
- Print both outputs.
Check Your Work
Run the script and confirm the unauthenticated request never reaches the controller.
Show solution
This solution uses a callable $next to represent the next layer in the request pipeline.
<?php
declare(strict_types=1);
/**
* @param array{user_id?: int} $request
*/
function requireLogin(array $request, callable $next): string
{
if (!isset($request['user_id'])) {
return '401 Authentication required';
}
return $next($request);
}
$controller = static function (array $request): string {
return 'Dashboard for user ' . $request['user_id'];
};
echo requireLogin(['user_id' => 42], $controller) . PHP_EOL;
echo requireLogin([], $controller) . PHP_EOL;
// Prints:
// Dashboard for user 42
// 401 Authentication required
Why This Works
The authenticated request reaches the controller. The unauthenticated request is stopped by middleware, which is exactly what auth middleware is supposed to do.