Feedback & Question: Middleware and Response in Slim 4

I’m done migrating to Slim 4 (from Slim 2 with a brief stop by Slim 3.) I got my Middleware working, but I wouldn’t say I’m happy with it.

The docs

Unclear

I don’t get it… The docs say:

Each middleware SHOULD invoke the next middleware and pass it Request and
Response objects as arguments.

Looks like a relic from Slim 3:

  • Slim 3 did use __invoke(Request $request, Response $response, callable $next).
  • Slim 4 uses __invoke(Request $request, RequestHandler $handler) – no
    $response.

Each middleware MUST return an instance of
Psr\Http\Message\ResponseInterface.

But then two out of three examples that come after it use Slim\Psr7\Response, with the third one using Psr\Http\Message\ResponseInterface.

So which one is it, Slim\Psr7\Response or Psr\Http\Message\ResponseInterface?

Bad example

The ExampleBeforeMiddleware example will break any redirect. Because it creates a new Response and only copies the old body, any redirect set further down the middlewares layers will be lost.

One way to fix it would be to replace the body of a response instead of creating a brand new Response. But how to do it? I understand we’re supposed to rely on StreamInterface but couldn’t find an example.

Another way would be to create a new Response, but copy the headers. Is there a one-liner to copy the headers, like there is for copying the body (with (string) $response->getBody())?

Accessing response in the Middleware

In v3, accessing the response before accessing the next middleware layer was
straightforward and the logic easy to read. e.g.:

public function __invoke(Request $request, Response $response, callable $next)
{
/**
 * Do stuff BEFORE passing to the next Middleware layer.
 * Example here is part of a Login middleware.
 * In this case, `before` checks for credentials and set cookies and redirect
 * as needed.
 */
$response = $this->before($request, $response);


/**
 * Invoke the next Middleware layer.
 * "Each middleware SHOULD invoke the next middleware and pass it
 * Request and Response objects as arguments."
 *
 * Don't run it for redirects as this is not needed (we're redirecting!)
 * and would break the app.
 */
if (! $response->isRedirect()) {
    $response = $next($request, $response);
}


/**
 * Do stuff AFTER the route is rendered.
 * e.g. `$response->getBody()->write('AFTER');`
 * e.g. `$response = $this->after($request, $response);` for symmetry.
 */
// Nothing to do for now.


/**
 * Finally, return the response.
 * "a middleware MUST return an instance of \Psr\Http\Message\ResponseInterface"
 */
return $response;
}

In v4, I’m confused… How to get the response BEFORE running the next level of
middleware?
Seems I have to create a new response all the time. So this makes me
wonder: in v3, was the $response at the beginning of _invoke actually always
empty too?

Thanks for the clarifications,
Fabien

Yes, that’s an relict from Slim 3 and should be fixed.

So which one is it, Slim\Psr7\Response or Psr\Http\Message\ResponseInterface ?

In a perfect world we should always implement against the PSR-7 / PSR-15 interfaces and not the real implementation. So it should be:

  • Psr\Http\Message\ServerRequestInterface (for the request object)
  • Psr\Http\Message\ResponseInterface (for the response object)

ExampleBeforeMiddleware

The ExampleBeforeMiddleware is also not a “correct” PSR-15 middleware.

Accodring to PSR-15 a middleware must implement the Psr\Http\Server\MiddlewareInterface interface. And the signature must look like this:

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

The ExampleBeforeMiddleware example will break any redirect . Because it creates a new Response and only copies the old body, any redirect set further down the middlewares layers will be lost.

You are right. This example in the docs is confusing (and wrong). Don’t overwrite the response object you already get from the handle method. If you do it, all other response data will get lost. Just use the response from the handle() method and it should be good.

Accessing the response

To get the response just invoke the handle method:

$response = $handler->handle($request);

You can also create a response by using the ResponseFactory before you invoke the handle() method.
For example:

$response = $this->responseFactory->createResponse();

To get access could als the ResponseFactory, you must inject it into your middleware via constructor injection.

How to get the response BEFORE running the next level of middleware?

Only by creating your own response via the ResponseFactory or by instantiating it manually:

$response = new \Slim\Psr7\Response();

Full example:

<?php

namespace App\Middleware;

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

/**
 * Middleware.
 */
final class ExampleMiddleware implements MiddlewareInterface
{
    /**
     * Invoke middleware.
     *
     * @param ServerRequestInterface $request The request
     * @param RequestHandlerInterface $handler The handler
     *
     * @return ResponseInterface The response
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // Optional: create a new response (depends on the use case)
        //$response = new \Slim\Psr7\Response();

        // Invoke the next middleware and fetch the response
        $response = $handler->handle($request);

        // Do something with the response
        $response = $response->withHeader('X-Demo', 'test');

        return $response;
    }
}