28 May 2017 —
Sometimes the nature of an application requires you to change the default framework’s way to structure error responses (like 404 and 405).
On this article I’m going to explain how to customize those responses when working with Zend Expressive 2.
Expressive 1. Error handler.
In Expressive 1, error handling was different.
There used to be an element called error handler, which was responsible of handling uncaught exceptions, but also, other errors, like the ones above (404 and 405).
When using the error handler it was easier to have an standard way to generate error responses for any kind of situation, like unmatched routes (404), routes requested with an incorrect HTTP verb (405) or uncaught exceptions (500), as well as other errors.
Expressive 2 behavior.
In expressive 2, the error handler is gone, replaced by a middleware which catches exceptions and lets you generate error responses. However, this middleware does not deal with 404 and 405 errors anymore. Now, those errors are handled by two new elements.
- 405: The routing middleware (which is now a separated middleware class) returns a 405 response when it detects that a request has been performed to an existing path but using an unsupported method. It adds an Allow header too, including the allowed HTTP verbs for that path.
- 404: The last middleware in the stack now receives the so called “default delegate”, which is responsible of generating a response when the stack is exhausted. The default implementation is the
NotFoundDelegate
, which as its name suggests, returns a response with status 404.
The problem with this two elements is the response body.
The first one returns an empty body by default, and the second one returns an error template (if a renderer service was registered) or a plain text response otherwise.
If your API is returning JSON responses (for example), this behavior is inconsistent and should be fixed.
Customize the response prototype
Both the RouteMiddleware
and the NotFoundDelegate
allow a PSR-7 response to be injected on them, to be used as the response prototype.
This way, we can define a consistent response that has the same structure regardless the error.
- 404 -
For the “not found” error, we have to define and register our own factory for the default delegate, which gets injected our customized response prototype:
Now register it on the config/autoload/dependencies.global.php
file:
And that should be it.
Of course, in this case that we always want to return the same response, we could just define another delegate which returns it, instead of using the built in implementation.
- 405 -
The “method not allowed” case is very similar, but with the RouteMiddleware
instead of the NotFoundDelegate
. However, there is one thing to take into account that we’ll see later.
First, let’s create our own factory:
Now we should register the middleware service and pipe it, but here’s the “problem”.
When doing this, my first approach was trying to override the Application::ROUTING_MIDDLEWARE
name, but it didn’t work.
Internally, when expressive detects that middleware name, it instantiates a RouteMiddleware
instance of its own, without the custom response prototype.
I have already created a question in ZF’s forum, because I’m not sure if that’s the intended behavior: https://discourse.zendframework.com/t/customize-response-prototype-on-routemiddleware/129
In the meantime, we are going to use a different middleware/service name, so let’s register it.
Now, we have to pipe our middleware instead of the built in Application::ROUTING_MIDDLEWARE
. Do it on config\pipeline.php
;
And that’s all.
If in the end, the “problem” described above is fixed somehow, I’ll update this article.
Conclusion
This demonstrates how flexible expressive is.
We have many possible approaches. Overwrite built-in components, customize its behavior… We are always in control of the application, and there’s no black magic involved.