Slim Middlewares

Table of contents

Loading...

Introduction

Middlewares are classes that can modify the request and response objects.

Before the HTTP request reaches the Slim Action, it passes through the middleware stack.

They are very handy for tasks that need to be done on every request, such as checking if the user is authenticated, figuring out the language of the user, enabling cross-origin requests for api endpoints or catching errors.

Flowchart

middleware-flowchart

Middleware stack

The middlewares are defined in the file middleware.php and are loaded during the initialization of the app.

File: config/middleware.php

use Slim\App;

return function (App $app) {
   // Slim internal middleware
    $app->addBodyParsingMiddleware();
    $app->add(\App\Application\Middleware\LocaleMiddleware::class);
    $app->add(\Odan\Session\Middleware\SessionStartMiddleware::class);
    $app->addRoutingMiddleware();
    $app->add(\App\Application\Middleware\ValidationExceptionMiddleware::class);  
    $app->add(\App\Application\Middleware\ErrorHandlerMiddleware::class);
};

The order in which middlewares are defined is crucial as it determines the sequence in which the middlewares code is executed.

Order of execution

When a request is received in Slim 4, it’s passed to the process method of the last middleware in the stack (due to the Last-In-First-Out, or LIFO, order).

This method can execute some preprocessing code, before calling $handler->handle($request), which passes the request to the process method of the next middleware in the stack (moving towards the top of the list). This continues until the first middleware in the stack is reached.

At this point, the handle method passes the request to the Action which executes code and returns a response.

Now the stack begins to unwind.
The first middleware’s process method finishes executing and returns a response.

This response travels back the stack to the next middleware (moving towards the last middleware that was added), which can then execute some post-processing code before returning the response to the next middleware.

This continues until the response reaches the last middleware, which does its post-processing and returns the final response.

The following example shows the order of execution with 3 middlewares:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class MiddlewareA implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // Preprocessing code here
        echo "1 MiddlewareA preprocessing\n";
        $response = $handler->handle($request);
        // Post-processing code here
        echo "1 MiddlewareA postprocessing\n";
        return $response;
    }
}

class MiddlewareB implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        echo "2 MiddlewareB preprocessing\n";
        $response = $handler->handle($request);
        echo "2 MiddlewareB postprocessing\n";
        return $response;
    }
}

class MiddlewareC implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        echo "3 MiddlewareC preprocessing\n";
        $response = $handler->handle($request);
        echo "3 MiddlewareC postprocessing\n";
        return $response;
    }
}

class TestAction
{
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        echo "Action code\n";
        return $response;
    }
}

If these middlewares are added to the stack in the order A, B, C and then a request is handled, the following output is produced:

3 MiddlewareC preprocessing
2 MiddlewareB preprocessing
1 MiddlewareA preprocessing
Action code
1 MiddlewareA postprocessing
2 MiddlewareB postprocessing
3 MiddlewareC postprocessing

Middleware on a specific route

Sometimes it is necessary to add a middleware to a specific route or route group only.
For instance, when the user must be authenticated for some actions but not others.

Middlewares can be added to a route or route group in the routes.php file by chaining the ->add function to the route definition containing the middleware class:

$app->group('/clients', function (RouteCollectorProxy $group) {
    // Routes that require authentication
})->add(App\Application\Middleware\UserAuthenticationMiddleware::class);

Middleware class

Middlewares need to implement the Psr\Http\Server\MiddlewareInterface interface. This interface requires the implementation of the process function that takes two parameters:

It must return a Psr\Http\Message\ResponseInterface object.

This is an example of a validation exception middleware that catches any ValidationException from the application and responds with a JSON response:

final readonly class ValidationExceptionMiddleware implements MiddlewareInterface
{
    public function __construct(
        private Responder $responder,
    ) {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        try {
            // If there is no validation exception, nothing is done and the request is passed to the next middleware
            return $handler->handle($request);
        } catch (ValidationException $validationException) {
            // Create response
            $response = $this->responder->createResponse();

            $responseData = [
                'status' => 'error',
                'message' => $validationException->getMessage(),
                // The error format is already transformed to the format that the frontend expects in the exception.
                'data' => ['errors' => $validationException->validationErrors],
            ];

            return $this->responder->encodeAndAddToResponse($response, $responseData, 422);
        }
    }
}

More on middlewares in the Slim 4 documentation.

^