Throwing Exceptions vs Response with >=400 codes

This is a design philosophy question, so I know that there is no right answer per se, but I was hoping to get a feeling of the community.

When it comes to handling non-successful requests, there are a few ways of handling what gets returned. The documents seem to be steering developers to throwing exceptions which Zend\Stratigility\Middleware\ErrorHandler can then catch and process. This seems to be very straight forward, and a nice way to indicate in code that something has gone wrong and to short-circuit the pipeline. I’ve built my program this way and it works for my code very well. I can log at various levels based off the Exception code with a listener attached to ErrorHandler, etc.

However, when you look at much of the code in the Expressing and Stratigility libraries, there looks to be more of an emphasis of generating a Response object with a code instead of throwing an exception.


if ($result->isMethodFailure()) {
    return $this->responsePrototype->withStatus(StatusCode::STATUS_METHOD_NOT_ALLOWED)
        ->withHeader('Allow', implode(',', $result->getAllowedMethods()));

I can see a case here where it’s part of the HTTP spec to return the Allowed methods, but this means that not only do I need to handle thrown exceptions, but need to check the response code to know if I have a good response or not. There is also the case that Zend\Expressive\Application::emitMarshalServerRequestException() doesn’t check to see if there is a Zend\Stratigility\Middleware\ErrorHandler::class declared - it skips directly to using the generator, so listeners I’ve attached to ErrorHandler never get called. @matthew I think you may have addressed this before on some ticket somewhere, but I can’t find it.

What I’m getting at is that there appears to be multiple ways and processes for handling non-successful requests: Throwables that will be handled via some sort of ErrorHandler (per docs and Stratagility classes); Return a Response object with appropriate code (RouteMiddleware); Throw the exception directly to an ErrorGenerator (emitMarshalServerRequestException()).

Is there a way to perhaps merge these approaches, or is his a case where the possible scenarios are so vast and different, a polyglot approach should be kept?


The reason this doesn’t use the ErrorHandler is because it happens outside the middleware pipeline. When run() is called, the pipeline has not yet been dispatched. In this particular case, we’re still in the process of getting a ServerRequestInterface instance ready to pass into the pipeline!

It really comes down to preference. In some cases, returning a response directly from your middleware may make a lot of sense: you can completely control the status code, headers returned, and, potentially, add content to the body (such as rendering a template, or providing a JSON or XML payload).

In some cases, however, you may have exceptions bubble out of code you invoke that you don’t really want to care about too much; you’ll handle what you know and care about, but lower level things like failure to connect to a database or cache server may be something you don’t know how to report, or are fine reporting as a generic error.

Generally speaking, I like to return a response when I have data and information I want to use to fully shape the response, but use an error handler otherwise.

One other possibility is what we’re presenting in the proposed Problem Details module (see my RFC. In that case, you’d provide the additional detail via your exception types themselves, and the error handler you use would then decide what details are useful (e.g., the ProblemDetails middleware would pull the title, type, detail, and any additional data!). This approach allows you to omit error handling for true errors from your middleware, and delegate it to your error handler middleware (singular or plural, based on your stack).

@matthew I like the Problem Details approach in that it could be used to create a unified error system as it’s both a middleware that could replace ErrorHandler and a Response-generating service that can cleanup how internal classes like Application and RouteMiddleware handle errors. I’ve commented in the other thread my questions and thoughts about the implementation.