Skip to content

Middleware Stack

Phexium uses PSR-15 compliant middleware for request/response processing. Middleware is registered in bootstrap.php and executes in LIFO order (last added = first executed).

PSR-15 Compatibility

All middleware implements Psr\Http\Server\MiddlewareInterface with its process() method signature. This ensures interoperability with any PSR-15 compliant framework or library.

Middleware Order

The order of middleware registration determines execution flow. Slim uses LIFO (Last In, First Out), meaning the last middleware added executes first on incoming requests.

Request/Response Flow

HTTP Request
1. ErrorMiddleware (outer - catches all exceptions)
2. ContentLengthMiddleware (adds Content-Length header on response)
3. UserContextMiddleware (demo only - loads user context)
4. SessionMiddleware (starts/saves session)
5. BodyParsingMiddleware (parses JSON/form/XML)
Route Handler + Route-specific middleware (RBAC)
Response bubbles back up through the stack

Registration Order in Code

In bootstrap.php, middleware is added in reverse execution order:

$app->addBodyParsingMiddleware();        // 5. Innermost (last to execute on request)
$app->add(SessionMiddleware::class);     // 4.
$app->add(UserContextMiddleware::class); // 3. Demo application only
$app->add(Whoops::class);                // 2. Error handler
$app->add(ContentLengthMiddleware::class); // 1. Outermost

Why Order Matters

Middleware Position Reason
ErrorMiddleware Outermost Catches exceptions from all inner middleware
ContentLengthMiddleware Early Needs complete response body to calculate length
UserContextMiddleware Before routes Routes need user context for RBAC
SessionMiddleware Before user context User context reads from session
BodyParsingMiddleware Innermost Handlers need parsed body data

Demo vs Starter Configuration

The demo application includes UserContextMiddleware for authenticated user context, while the starter template omits it:

Demo application (config/demo/bootstrap.php):

$app->add(SessionMiddleware::class);
$app->add(UserContextMiddleware::class);  // Loads authenticated user

Starter application (config/starter/bootstrap.php):

$app->add(SessionMiddleware::class);
// No UserContextMiddleware - add when implementing authentication

Creating Middleware

final readonly class MyMiddleware implements MiddlewareInterface
{
    #[Override]
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // Before: modify request
        $request = $request->withAttribute('custom', 'value');

        // Call next middleware
        $response = $handler->handle($request);

        // After: modify response
        return $response->withHeader('X-Custom', 'value');
    }
}

Global vs Route Middleware

Global Middleware

Applied to all routes via $app->add():

$app->add(SessionMiddleware::class);

Route Middleware

Applied to specific routes:

$app->get('/admin', [AdminController::class, 'index'])
    ->add($rbac->forPermission('admin.access'));

Short-Circuiting

Middleware can return a response without calling the next handler:

if (!$this->isAuthorized($request)) {
    return $this->responseFactory->createResponse(403);
}
return $handler->handle($request);

See Also