Skip to content

Middleware Implementation

How to Implement Middleware in Slim 4 Framework?

Section titled “How to Implement Middleware in Slim 4 Framework?”

There are two main ways to implement middleware in Slim 4 Framework:

  1. Class-Based Middleware (PSR-15, recommended way to implement middleware)
  2. Function-Based Middleware (Closure-Based)
Example of Function-Based Middleware
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
$app = AppFactory::create();
$middleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
// Run some code before the request is handled.
// Pass control to the next middleware or route handler.
$response = $handler->handle($request);
// Run some code after the request is handled.
$response = $response->withHeader('X-Custom-Header', 'value');
return $response;
};
// Add the middleware to the application
$app->add($middleware);

Implementing Class-Based Middleware (PSR-15)

Section titled “Implementing Class-Based Middleware (PSR-15)”

PSR-15 is the PHP Standard Recommendation for HTTP middleware. It provides a standardized interface that makes middleware portable and reusable across different applications and frameworks (e.g. Slim 4 Framework, Symfony, Laravel, etc.).

PSR-15 defines how middleware should be structured:

  • Middleware interface: For processing requests/responses
  • Request handler interface: For handling the request
  • Standardized method signatures: Ensures compatibility

Benefits of PSR-15:

  • Code reusability across projects.
  • Clear, predictable structure.
  • Industry best practice.
  • Better IDE support and type hinting.

Your middleware class must implement Psr\Http\Server\MiddlewareInterface.

This interface requires one method:

public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface

Parameters:

  • $request The incoming HTTP request
  • $handler The next middleware/handler in the chain

Returns:

  • A response object

Your middleware class structure should look like this:

namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class YourMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
// (optional:) Execute some code before the request is handled.
// (required:) Pass control to the next middleware or route handler.
$response = $handler->handle($request);
// (optional:) Execute some code after the request is handled.
// e.g.,:
// $response = $response->withHeader('X-Custom-Header', 'value');
return $response;
}
}

Inside the process() method, you can:

Before the route handler executes:

  1. Inspect or modify the request
  2. Check conditions (authentication, validation, etc.)
  3. Return early if needed (e.g., unauthorized)

After the route handler executes:

  1. Modify the response
  2. Add headers
  3. Log information

Example flow:

public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
// BEFORE: Check something in the request
if ($someConditionFails) {
// Return early, don't call next middleware
$response = new Response();
return $response->withStatus(401)
}
// Call the next middleware/handler
$response = $handler->handle($request);
// AFTER: Modify the response
return $response->withHeader('X-Custom-Header', 'value');
}

If your middleware needs services (database, session, etc.), inject them through the constructor:

class AuthMiddleware implements MiddlewareInterface
{
private $sessionManager;
public function __construct(SessionManager $sessionManager)
{
$this->sessionManager = $sessionManager;
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
// Use $this->sessionManager here
}
}

Once created, instantiate and add it to your application:

// Create instance
$sessionManager = new SessionManager();
$authMiddleware = new \App\Middleware\AuthMiddleware($sessionManager);
// Add to application, route, or group
$app->add($authMiddleware);

$handler->handle($request) passes control to the next middleware in the chain or the route handler if this is the last middleware.

Important: You must call this method unless you want to stop the chain (e.g., for authentication failure).

PSR-7 requests are immutable. To modify a request:

// Wrong - doesn't work
$request->setAttribute('user', $user);
// Correct - creates new request object
$request = $request->withAttribute('user', $user);

Same applies to responses:

// Correct way to add headers
$response = $response->withHeader('X-Custom', 'value');

If you need to stop the middleware chain (e.g., authentication fails), return a response without calling $handler->handle():

if (!$authenticated) {
// Stop here, don't proceed to next middleware
return $response->withStatus(401);
}

Before you finish, verify:

  • Class implements MiddlewareInterface
  • Has process() method with correct signature
  • Calls $handler->handle($request) (unless stopping early)
  • Returns a ResponseInterface object
  • Uses immutable request/response methods (withHeader, withAttribute, etc.)
  • Handles both success and failure cases
  • Constructor accepts any needed dependencies
  • Properly namespaced in your project

  • Applies to every single route in your application.
$middlewareInstance = new \App\Middleware\YourMiddleware();
$app->add($middlewareInstance);
// Or:
$app->add(YourMiddleware::class);

Order matters: Add from outermost to innermost layer.


  • Applies to one specific route only.
$app->get('/profile', function ($request, $response) {
// Route handler
})->add($middlewareInstance);

Multiple middleware on one route:

$app->get('/admin/dashboard', function ($request, $response) {
// Route handler
})->add($authMiddleware)->add($adminMiddleware);

The middleware closest to the route executes first for the request.


  • Applies to all routes within a group.
$app->group('/admin', function ($group) {
$group->get('/dashboard', function ($request, $response) {
// Dashboard route
});
$group->get('/users', function ($request, $response) {
// Users route
});
})->add($adminMiddleware);

All routes under /admin will use the $adminMiddleware.

Nested groups with multiple middleware:

$app->group('/api', function ($group) {
$group->group('/v1', function ($group) {
$group->get('/users', function ($request, $response) {
// API endpoint
});
})->add($versionMiddleware);
})->add($apiAuthMiddleware);

Routes inherit middleware from parent groups.