Customize response prototype on RouteMiddleware

Hi!
I’m not sure if this is the correct place to publish this. Maybe I should open an issue on github. Just let me know it.

A few days ago I asked on twitter what was the best way to customize the response injected on the RouteMiddleware. https://twitter.com/acelayaa/status/864374315248123904

However, a while later I ended up defining my own middleware factory which returns a RouteMiddleware with my custom response prototype. Something like this (this is simplified):

<?php
use Zend\Expressive\Middleware\RouteMiddleware;

class RoutingMiddlewareFactory
{
    public function __invoke(ContainerInterface $container)
    {
        $respProto = new JsonResponse([
            'error' => 'METHOD_NOT_ALLOWED',
            'message' => 'Method not allowed',
        ], 405);
        return new RouteMiddleware($container->get(RouterInterface::class), $respProto);
    }
}

The problem was that it is not possible to register this factory using the Application::ROUTING_MIDDLEWARE name.
When that name is used for a middleware, the Application always instantiates a new RouteMiddleware, instead of trying to fetch a service from the container. I think the problem is in the prepareMiddleware method in MarshalMiddlewareTrait.
I haven’t checked it, but I believe something similar happens if you use the Application::DISPATCH_MIDDLEWARE name

The solution was easy, I think. I just used another name and piped that middleware instead of Application::ROUTING_MIDDLEWARE, however it feels a little bit counterintuitive.

Is this behavior intended?

You can actually set the response prototype earlier - in the Application class before you start piping anything. In your index.php, you can set something like

/** @var Application $engine */
$engine = $container->get(Application::class);
$engine->setResponsePrototype(new JsonResponse([
            'error' => 'METHOD_NOT_ALLOWED',
            'message' => 'Method not allowed',
        ], 405));

Short answer: Yes, it’s intended.

Longer answer…

Those constants (ROUTING_MIDDLEWARE, DISPATCH_MIDDLEWARE) exist because of two decisions made for Expressive v1:

  • we chose configuration-centric pipelines and routing; and
  • at the time, the routing and dispatch middleware were internal methods of the Application instance.

For v2, we changed our minds on both decisions. The routing and dispatch middleware are now separate classes, and we encourage usage of the programmatic API for both pipelines and routing.

We still offer the methods pipeRoutingMiddleware() and pipeDispatchMiddleware(), however. These are shorthands for calling pipe() with the associated classes. Since these are the cow path, we figured making them easy to remember and type was important.

What that means, however, is if you want to provide a custom implementation of either, or, as you do, provide alternative constructor arguments in your own factory, you need to pipe the alternatives explicitly using the pipe() syntax, and omit the associated pipe*Middleware() call.

Ok then. That’s what I did, so perfect :slight_smile: