Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Exception Handler Laravel Package

phphd/exception-handler

PHPhD Exception Handler offers a structured way to manage exceptions in Laravel/PHP applications, with configurable handling and cleaner error responses for more predictable debugging and user-facing failures.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish in two commands:

composer require phphd/exception-handler
php artisan vendor:publish --tag="exception-handler-config"

Package discovery registers the service provider. On older skeletons or with discovery disabled, add it to bootstrap/providers.php.

Where to look first: the published config. It's the handler registry and the resolution order — the file you'll open every time something "doesn't fire." Second stop is the handler contract (the handle() signature and required return type); you're implementing an interface, not extending a base class.

Scaffold your first handler:

php artisan make:exception-handler PaymentFailedExceptionHandler

First use case: pick your noisiest if ($e instanceof ...) chain — auth, domain/payment failures, or third-party API errors. Those benefit most from one-class-per-exception. Implement one, register it, delete the corresponding branch, verify with a feature test that asserts status + JSON shape.

final class PaymentFailedExceptionHandler
{
    public function handle(PaymentFailedException $e): Response
    {
        report($e); // skip for expected control flow
        return response()->json([
            'message' => 'Payment could not be processed.',
            'code'    => 'payment_failed',
        ], 402);
    }
}

Migrate one exception per PR. Don't rewrite your whole error path in a single branch — you lose the ability to bisect regressions.

Implementation Patterns

Day-to-day, the package behaves like a routing table for failures: exception in → handler matched → response or fall-through. Work in these patterns.

  • One handler per exception, return a Response or null. null means "fall through to the next handler, eventually the framework default." Use it for log-and-continue; use a real response for terminal cases.
  • Order is the spec. Register the most specific exception types first, broad Throwable last. When your ValidationException or AuthenticationException is being swallowed, it's an ordering problem, not a handler bug — reorder, don't debug the handler.
  • Config registry vs attribute on the exception. Attributes read well for exceptions you own; the config array is the only option for vendor exceptions (Stripe, Guzzle, PDO). Pick one convention per repo — mixing makes precedence hard to reason about.
  • Split web and API. Either branch inside handle() on $request->expectsJson(), or register two handlers and let the first that can answer win. Branching is simpler; splitting scales better once the JSON envelope is standardized.
  • Standardize the envelope once. Route every JSON handler through a small ApiErrorResponse helper — status, code string, message, request/trace ID. Consistency is the whole value proposition.
  • Report and render are separate. Call report($e) explicitly for unexpected failures; skip it for expected control flow (rate limits, 404s, domain validation) so Sentry/Bugsnag stays signal-rich.
  • Module-scoped registration. In modular codebases, register from each module's service provider rather than one giant config array — ownership stays local and removal is safe.
  • Test at the HTTP boundary. Feature tests asserting status + JSON shape are what actually validate precedence. A unit test on a single handler won't catch a handler that never fires.
  • Daily loop: reproduce via feature test → read config order → add or adjust handler → config:clear → re-run.
  • Wrap it in a thin internal adapter. Your handler contract should be yours, with the package behind it — the dependency stays swappable and the envelope stays yours.

Gotchas and Tips

  • Double registration on Laravel 11+. If you also wire the same exceptions through bootstrap/app.php ->withExceptions(), precedence becomes non-obvious and debugging turns into archaeology. One owner per exception — decide and document it.
  • Silent no-ops. A handler that returns nothing looks like it fired while the framework quietly produced the real response. Always return explicitly.
  • Config caching is the #1 "it doesn't run" cause. The registry is config and gets cached in production. After any registration change: php artisan config:clear (and config:cache again).
  • Never leak internals. $e->getMessage() and $e->getTraceAsString() must not reach production clients. Log them; return a stable code + message.
  • Console and queue context. HTTP-shaped handlers also fire for artisan commands and jobs. Guard with app()->runningInConsole() (or check request type) before returning an HTTP-shaped response.
  • withoutExceptionHandling() in tests is the fastest way to prove a handler is masking a genuine bug — use it liberally while debugging.
  • Debugging recipe: temporarily register a catch-all handler that dd($e)s. Confirm it fires, delete it, retest. This isolates "never matched" from "matched but response was wrong" in one step.
  • Extension point: the resolution pipeline itself — wrap it to add cross-cutting concerns (correlation IDs, metrics, redaction) in one place instead of per handler.
  • Read the repo, not just the docs. The package description ("Bundle implementing exception handler via Symfony Messenger component") and the Laravel-shaped documentation don't line up cleanly. Confirm the dispatch path actually runs in your bootstrap/container with an end-to-end feature test before depending on it — especially if you're not on a stock Laravel skeleton.
  • Low-activity signal. One star, low activity score, and a link-only 1.1.1 changelog mean you should read the actual 1.1.0→1.1.1 diff, confirm the supported Laravel/PHP matrix against your fleet, and spike in one non-critical service before standardizing.
  • Upgrade discipline. 1.1.1 is a patch with no documented API changes — pin ^1.1. Between minors, the handler contract is the piece most likely to shift; treat it as critical-path code (reviewed, pinned, documented rollback).
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo