Middleware
Global and per-route PSR-15 middleware pipeline.
Table of contents
Overview
Waypoint supports PSR-15 middleware at two levels: global (runs for every matched route) and route-level (applied to specific routes).
Middleware execution order is FIFO: global middleware runs first, then route-specific middleware, then the controller handler.
PSR middleware packages (psr/http-server-middleware, psr/http-server-handler) are optional dependencies. Install them if you use middleware: composer require psr/http-server-middleware psr/http-server-handler.
Global Middleware
Global middleware is added to the Router and runs for every matched route:
$router->addMiddleware(CorsMiddleware::class);
$router->addMiddleware(new RateLimitMiddleware(limit: 100));
When provided as a class name string, middleware is resolved from the PSR-11 container. When provided as an instance, it is used directly.
Route-Level Middleware
Apply middleware to specific routes during registration:
$registrar->get('/admin/dashboard', [AdminController::class, 'dashboard'],
middleware: [AdminAuthMiddleware::class],
);
Or via attributes:
use AsceticSoft\Waypoint\Attribute\Route;
#[Route('/api/users', middleware: [AuthMiddleware::class])]
class UserController
{
#[Route('/', methods: ['GET'])]
public function list(): ResponseInterface { /* ... */ }
}
Group Middleware
Apply middleware to a group of routes:
$registrar->group('/api', function (RouteRegistrar $registrar) {
$registrar->get('/users', [UserController::class, 'list']);
$registrar->get('/posts', [PostController::class, 'list']);
}, middleware: [ApiAuthMiddleware::class, RateLimitMiddleware::class]);
Group middleware is prepended to route-level middleware. With nested groups, middleware accumulates from outer to inner groups.
Execution Order
For a route with both global and route-level middleware, the execution order is:
- Global middleware (in registration order)
- Group middleware (outer to inner)
- Route-level middleware (in declaration order)
- Controller handler
Request → CorsMiddleware → AuthMiddleware → RouteMiddleware → Controller
↓
Response ← CorsMiddleware ← AuthMiddleware ← RouteMiddleware ← Controller
Each middleware can short-circuit the pipeline by returning a response without calling $handler->handle($request). This is useful for authentication checks, rate limiting, etc.
Writing Middleware
Middleware must implement Psr\Http\Server\MiddlewareInterface:
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class TimingMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
): ResponseInterface {
$start = microtime(true);
$response = $handler->handle($request);
$duration = microtime(true) - $start;
return $response->withHeader('X-Response-Time', round($duration * 1000) . 'ms');
}
}