Middleware priority ordering
Concept
Middleware priority ordering ensures that middleware runs in the correct order regardless of the order routes or groups declared them. Some middleware has dependencies — session must run before auth (auth reads from session), CORS headers must be set before any output, etc.
Why ordering matters: If auth middleware runs before session middleware, $_SESSION isn't initialized yet and Auth::user() fails. If logging runs after the response is sent, timing data is wrong.
Priority list: A framework defines a global priority list — an ordered array of middleware class names. When building the pipeline, middleware is sorted according to this list. Any middleware not in the priority list runs in the order it was added, appended after priority middleware.
Laravel's $middlewarePriority: App\Http\Kernel::$middlewarePriority defines the order. Session → Auth → Throttle → etc. When middleware appears in both route definition and priority list, the priority list position wins.
Sorting algorithm:
- Extract middleware that appears in the priority list.
- Sort them by their position in the priority list.
- Prepend them to the remaining (non-priority) middleware.
Terminating middleware: TerminableMiddleware interface — terminate(Request $request, Response $response): void. Called AFTER the response is sent to the browser. Used for logging/cleanup without delaying the response.
Priority for performance: Put expensive middleware (DB queries, external API calls) later in the chain, after cheap middleware (CORS, basic header checks) that might short-circuit.
Code Example
<?php
namespace Framework\Http;
class MiddlewarePriority
{
// Default priority order — earlier in array = runs first
private array $priority = [
\Framework\Http\Middleware\HandleCors::class,
\Framework\Http\Middleware\StartSession::class,
\Framework\Http\Middleware\HandleCsrf::class,
\App\Http\Middleware\Authenticate::class,
\App\Http\Middleware\EnsureEmailVerified::class,
\App\Http\Middleware\RateLimiter::class,
];
public function sort(array $middleware): array
{
// Separate priority middleware from the rest
$prioritized = [];
$nonPrioritized = [];
foreach ($middleware as $mw) {
$className = is_object($mw) ? get_class($mw) : $mw;
$position = array_search($className, $this->priority);
if ($position !== false) {
$prioritized[$position] = $mw;
} else {
$nonPrioritized[] = $mw;
}
}
ksort($prioritized); // Sort by priority position
return array_merge(array_values($prioritized), $nonPrioritized);
}
}
// Terminable middleware — runs after response is sent
interface TerminableMiddleware
{
public function terminate(
\Psr\Http\Message\ServerRequestInterface $request,
\Psr\Http\Message\ResponseInterface $response
): void;
}
class RequestLogger implements \Psr\Http\Server\MiddlewareInterface, TerminableMiddleware
{
private float $startTime;
public function process($request, $next): \Psr\Http\Message\ResponseInterface
{
$this->startTime = microtime(true);
return $next->handle($request);
}
public function terminate($request, $response): void
{
// Runs after response is sent — no delay to user
$duration = microtime(true) - $this->startTime;
\Illuminate\Support\Facades\Log::info('Request completed', [
'method' => $request->getMethod(),
'uri' => $request->getUri()->getPath(),
'status' => $response->getStatusCode(),
'duration' => round($duration * 1000, 2) . 'ms',
]);
}
}
// After emitting response, call terminate on terminable middleware
// In the emitter:
// $emitter->emit($response);
// foreach ($pipeline->getTerminableMiddleware() as $mw) {
// $mw->terminate($request, $response);
// }