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 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:
$request
of typePsr\Http\Message\ServerRequestInterface
$handler
of typePsr\Http\Server\RequestHandlerInterface
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.