zeuxisoo/slim-whoops
Integrates the Whoops error handler with Slim Framework, replacing default error pages with detailed debugging output, stack traces, and a clean interface during development. Install via Composer and register the middleware/provider to get started quickly.
Understand what you're installing: this is a ~100-line bridge, not a framework feature. zeuxisoo/slim-whoops registers filp/whoops as Slim middleware so uncaught exceptions render a full-page trace with source snippets, request/server dumps, and clickable editor links. Laravel's equivalent is Ignition + APP_DEBUG; the CLI equivalent is Collision. If you're a Laravel dev, you'll only touch this on a Slim microservice, a legacy app you're maintaining, or during a Slim → Laravel migration audit.
Install and pin to your Slim major — the constructor signature changes across majors:
composer require zeuxisoo/slim-whoops
composer show zeuxisoo/slim-whoops slim/slim filp/whoops # verify the trio is compatible
Roughly: v1.x targets Slim 2, v2.x targets Slim 3, v3.x targets Slim 4. Confirm against the README for the version you actually resolved, because the Slim 4 variant needs extra constructor arguments.
Where to look first
README.md — the Slim 3 and Slim 4 registration snippets are the whole API surface.src/WhoopsMiddleware.php — read it end to end (10 minutes). You'll see exactly which settings keys are consumed and which are forwarded to Whoops.vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php — the real configuration surface (setEditor, setPageTitle, setApplicationPaths, custom assets). Most "the package can't do X" complaints are actually Whoops features reached through the settings array.Minimal Slim 3 registration — in public/index.php, which is Slim's equivalent of bootstrap/app.php:
$app = new \Slim\App($container);
if (getenv('APP_ENV') !== 'production') {
$app->add(new \Zeuxisoo\Whoops\Slim\WhoopsMiddleware([
'enable' => true,
'whoops.editor' => 'vscode',
]));
}
$app->run();
Minimal Slim 4 registration — resolver and response factory come first, and ordering matters (see Gotchas):
$app->addRoutingMiddleware();
$app->addErrorMiddleware(true, true, true); // displayErrorDetails must be true too
$app->add(new \Zeuxisoo\Whoops\Slim\WhoopsMiddleware(
$app->getCallableResolver(),
$app->getResponseFactory(),
[
'enable' => true,
'whoops.editor' => 'phpstorm',
]
));
$app->run();
First use case: add a throwaway route that does throw new \RuntimeException('whoops smoke test');, hit it, confirm you get the Whoops page rather than Slim's grey 500, then click a stack frame and verify your editor opens. That single loop validates install, middleware order, and editor config at once.
Gate on environment in code, not in config. Slim has no APP_DEBUG and no config publishing, so the settings array in the front controller is your config file. Keep it explicit and boring:
$debug = in_array(getenv('APP_ENV') ?: 'production', ['local', 'development', 'testing'], true);
$app->add(new \Zeuxisoo\Whoops\Slim\WhoopsMiddleware([
'enable' => $debug,
'whoops.editor' => getenv('WHOOKS_EDITOR') ?: 'vscode',
'whoops.page_title' => 'Application Error',
'whoops.application_paths' => [dirname(__DIR__) . '/src'],
]));
whoops.application_paths is the highest-value option day to day: it trims Composer's vendor/ frames out of the headline trace so you see your code first. Without it, every stack starts in vendor/slim/slim.
Editor links. Supported identifiers include sublime, textmate, emacs, macvim, phpstorm, idea, vscode, atom, espresso. For anything else, drop to Whoops directly and register a closure — this is the main extension point people miss:
$run = new \Whoops\Run();
$run->pushHandler(tap(new \Whoops\Handler\PrettyPageHandler(), function ($h) {
$h->setEditor(fn ($file, $line) => "myeditor://open?file={$file}&line={$line}");
}));
Content negotiation for API routes. Whoops renders HTML by default, so Accept: application/json clients get a useless HTML blob. Whoops stops at the first handler that returns a response, so build the stack per request:
$run = new \Whoops\Run();
if (str_contains($request->getHeaderLine('Accept'), 'application/json')) {
$run->pushHandler(new \Whoops\Handler\JsonResponseHandler());
} else {
$run->pushHandler(new \Whoops\Handler\PrettyPageHandler());
}
Pair it with real logging. Whoops is a developer-facing renderer, not an error reporter. In Slim 4, keep addErrorMiddleware(..., $logger) or your Monolog handler doing the logging; let Whoops own only the response body in dev. In production, enable => false plus a PlainTextHandler or your logger — never the pretty page.
Build the middleware through the container. Since the settings array grows, resolve the middleware from a factory so it reads from your Slim config instead of hardcoding values in public/index.php. Slim 4's $app->add() accepts a class/ID string when the callable resolver can build it.
Testing. In phpunit.xml or the test bootstrap, set APP_ENV=testing and keep enable => false; otherwise Whoops swallows the exception into an HTML response and expectException never fires the way you expect.
addRoutingMiddleware()/addErrorMiddleware() so it wraps them; if Slim's ErrorMiddleware sits outside, it converts the exception to a response first and you'll stare at a generic error page wondering why Whoops "doesn't work". Also set displayErrorDetails => true — false short-circuits before Whoops sees anything useful.CallableResolverInterface, ResponseFactoryInterface, array $settings. Getting this wrong is a TypeError on boot, not a helpful message.register_shutdown_function cases, parse errors, and memory exhaustion never make it there. If you want those covered, call (new \Whoops\Run())->register() at the very top of the front controller — that's the pattern the middleware doesn't replace.\Exception vs \Throwable. On older releases, PHP 7+ Error/TypeError instances can slip past the catch. Check the catch clause in src/WhoopsMiddleware.php before relying on it for type errors.$_ENV, cookies, session data, request headers, and full source. There is no access control on it. Treat enable => true in production as a security incident. The settings array defaults to enabled in some versions — never register the middleware unconditionally.vscode://file/... with an absolute path resolved from the PHP process, so under Docker, Vagrant, or a remote host the path doesn't exist locally. Either map paths via setEditor with a closure that rewrites the prefix, or accept plain text paths in remote environments.whoops. are forwarded to the Whoops Run; enable is consumed by the middleware; everything else is ignored without a warning. When a setting "doesn't apply", dump the array and diff it against src/WhoopsMiddleware.php before assuming a package bug.var_dump in a controller, a stray echo), Whoops can render an incomplete or corrupted page. Enable output_buffering in the dev PHP ini to give it a clean slate.filp/whoops is likewise in maintenance mode. That's fine for a dev-only renderer — but don't build production observability on it, and don't expect PHP-version support to land quickly. The honest fallback is ~30 lines: $run = new \Whoops\Run(); $run->pushHandler(new PrettyPageHandler()); $run->register(); in the front controller. If the middleware API ever breaks after a Slim upgrade, that's your escape hatch, and it's worth knowing before you commit to the dependency.How can I help you explore Laravel packages today?