Middleware Pipeline
Pipeline execution is deterministic and test-backed.
Order guarantee
Execution order is:
- global middleware
- group middleware
- route middleware
- handler
Accepted middleware forms
- class implementing
BetterRoute\Middleware\MiddlewareInterface - callable
(RequestContext $context, callable $next): mixed - class-string resolvable via
middlewareFactoryor zero-arg constructor
Short-circuit behavior
A middleware may return early and skip downstream calls. This is used for:
- auth rejection
- cached response replay
- rate-limit rejection
Factory-based resolution example
$router->middlewareFactory(function (string $class): mixed {
if ($class === JwtAuthMiddleware::class) {
return new JwtAuthMiddleware($verifier, ['content:*'], new WpClaimsUserMapper());
}
return null;
});
Common mistakes
- Returning non-callable from factory
- Not handling constructor dependencies for class-string middleware
- Mutating context without returning
$next($newContext)
Validation checklist
- middleware with ctor args is resolved by factory
- short-circuit does not execute handler
- thrown exceptions are normalized to error envelope
Identity-aware default keys (v0.3.0)
CachingMiddleware, IdempotencyMiddleware, and RateLimitMiddleware derive default keys from the request identity:
auth.userId > 0→"{provider}:user:{userId}"auth.subject(non-empty) →"{provider}:sub:{subject}"RateLimitMiddlewareonly: client IP fallback →"ip:{clientIp}"- otherwise →
"guest"
Pass an explicit keyResolver to keep pre-v0.3.0 keys.