Middleware & CORS#
src/Pages/middleware.php (optional) wraps the dispatch of every route in a single closure.
<?php
use Polidog\Relayer\Http\Request;
return function (Request $request, Closure $next): void {
if (null === $request->header('x-api-key')) {
\http_response_code(401);
echo '{"error":"missing api key"}';
return; // short-circuits if $next() is not called
}
$next($request);
};- Just one closure. There is no chain runner; compose by hand when you
want several (Composing multiple middleware).
- Like
route.php, declaration-free (return only, evaluated on every
request).
- If
$next($request)is not called, the downstream is not run and it
ends (401 / 429, etc.).
- The framework's defer / profiler endpoints deliberately run outside
this middleware.
CORS#
Do not hand-write CORS; use the built-in middleware.
<?php
use Polidog\Relayer\Http\Cors;
return Cors::middleware([
'origins' => ['https://app.example.com'],
// 'methods', 'headers', 'credentials', 'maxAge' are optional
]);To allow all origins, use ['origins' => ['*']]. Cors::middleware() also handles responding to preflight (OPTIONS).
Composing multiple middleware#
middleware.php can return only one closure, and there is no chain runner (by design). To stack several behaviors, compose by hand: pass the inner middleware as the $next of the outer one. Every middleware has the same fn(Request $request, Closure $next) signature, so the inner one is simply handed in as "the thing to continue to".
Minimal form:
<?php
// a is the outer layer, b the inner one, then the real route
return fn (Request $r, Closure $next) => $a($r, fn (Request $r) => $b($r, $next));Realistic example — tag the response with a request id, then delegate to CORS, then the route:
<?php
declare(strict_types=1);
use Polidog\Relayer\Http\Cors;
use Polidog\Relayer\Http\Request;
$cors = Cors::middleware([
'origins' => ['*'],
'methods' => ['GET', 'POST', 'OPTIONS'],
]);
return function (Request $request, Closure $next) use ($cors): void {
if (!\headers_sent()) {
\header('X-Request-Id: ' . \bin2hex(\random_bytes(8)));
}
// $cors answers OPTIONS preflights itself and, for real requests,
// adds the headers and continues to $next (the real route).
$cors($request, $next);
};Key points:
- Order is outer to inner. Code before
$next($request)is
pre-processing; code after it runs on the way back out.
- Any layer can short-circuit. If a layer does not call
$next,
neither the inner middleware nor the route runs (auth 401, rate limit 429, maintenance, CORS preflight, etc.).
- You can pass a different
Requestto$next. A layer that
substitutes it (e.g. $next($request->withPath(...))) makes the inner layers and the route see the substituted request.
- Three or more compose with the same nesting:
fn ($r, $n) => $a($r, fn ($r) => $b($r, fn ($r) => $c($r, $n))), where each of $a/$b/$c is an fn(Request, Closure) middleware.
Change history (2)
- Add the composing-multiple-middleware section
- Created