Skip to content
docsv0.17.0

ExceptionMiddleware

Turns an unhandled throwable into a RESPONSE, in the shape the caller asked for. Measured on a fresh app before this existed (greenhouse decisions/0215, F3): a controller that throws answered `500` with a **zero-byte body** and `Content-Type: text/html` whatever the request's `Accept` said — and with `display_errors` on, which the app does not control, the exception message, the file path and the whole stack trace went to the CLIENT. A JSON client got HTML; a browser got nothing. ── THE MESSAGE NEVER CROSSES, NOT EVEN IN DEBUG ──────────────────────────────────────────────────── `decisions/0215` asks for the detail under a declared `debug`, and for an authority exception's message never to leak. The obvious build is a marker interface the authority exceptions implement — and it was rejected after measuring it: `milpa/auth`, which owns five of the six authority exceptions in the family, does not depend on `milpa/core`, so the marker could not live where the framework's exception contracts live. Worse than the edge is the shape: a marker somebody must REMEMBER to add is a default that fails open, and an authority exception written next year would leak until someone noticed. So the rule is structural instead of enumerated: **no exception message and no stack trace ever reaches the client.** Debug adds the class, the file and the line — enough to find it — and the message and the trace go to the log, joined to the response by a reference the body carries. A rule that cannot be forgotten beats a list that can. ── WHAT IT DELIBERATELY DOES NOT DO ──────────────────────────────────────────────────────────────── It does not map exception types to status codes. Everything unhandled is a 500, because deciding that some exception means 403 needs a contract that says which — the same enumeration problem — and a wrong guess turns a bug into a silent «you are not allowed». A 403 is something a policy DECIDES, not something a failure is guessed to be.

ExceptionMiddleware::__construct()

public function __construct(Psr\Http\Message\ResponseFactoryInterface $responses, ?Psr\Log\LoggerInterface $logger = null, bool $debug = false):

Parameters

Parameters of __construct()
NameTypeDescription
$responsesPsr\Http\Message\ResponseFactoryInterface
$logger?Psr\Log\LoggerInterface
$debugbool

ExceptionMiddleware::process()

public function process(Psr\Http\Message\ServerRequestInterface $request, Psr\Http\Server\RequestHandlerInterface $handler): Psr\Http\Message\ResponseInterface

Runs the rest of the pipeline, and answers a rendered 500 if anything escapes it.

Parameters

Parameters of process()
NameTypeDescription
$requestPsr\Http\Message\ServerRequestInterface
$handlerPsr\Http\Server\RequestHandlerInterface