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

Catchable Bundle Laravel Package

forci/catchable-bundle

Symfony bundle for Forci Catchable that brings catchable exception handling to PHP applications. It integrates with Symfony’s error and exception workflow, helping centralize failure handling, reduce repetitive try/catch blocks, and keep controllers clean and consistent.

View on GitHub
Deep Wiki
Context7

Getting Started

Before anything else, confirm what you're actually holding. forci/catchable-bundle is a Symfony bundle, not a Laravel package. There is no extra.laravel auto-discovery, no service provider, and no config/catchable.php to publish. If you're on Laravel, "getting started" means deciding between two paths: read the source as a design reference (recommended), or stop entirely. If you're on a Symfony app, the steps below apply directly.

If you're on Symfony:

  1. Read composer.json first. A 0-star bundle with 0 dependents is frequently pinned to an older Symfony major. Check the require block for symfony/framework-bundle, php, and the doctrine/annotations version before you run anything. If it wants Symfony 3/4 while you're on 6/7, the install will fail or drag in a conflicting graph — resolve that before writing a line of code.
  2. Install and register. composer require forci/catchable-bundle. With Flex it may auto-register via the bundle's Resources/config/bundles.php recipe; otherwise add the FQCN (typically Forci\Bundle\CatchableBundle\CatchableBundle) to config/bundles.php. Copy the class name from the repo — don't guess it.
  3. Locate the configuration surface in this order: README → src/DependencyInjection/Configuration.php (the config tree and alias) → src/Resources/config/*.yml (service definitions) → any EventSubscriber/EventListener classes (this is where it hooks kernel.exception). Verify the alias resolves with bin/console debug:config <alias> and the wiring with bin/console debug:container --tag=kernel.event_subscriber.
  4. First use case: annotate one controller action. Put the catchable annotation/attribute on a single action with a handler that maps a domain exception (e.g. OrderNotFound) to a structured 404/422 JSON response. Run that one endpoint, throw the exception deliberately, and confirm the response shape before scaling to other controllers.

If you're on Laravel: treat this as a reference implementation of a pattern you'll re-implement in ~30 lines. The equivalent surface in Laravel is Illuminate\Foundation\Exceptions\Handler::renderable() / reportable(), plus render() methods on exception classes. Read src/ for the mapping idea, then close the tab.

Implementation Patterns

The core pattern. The bundle lets you attach exception→response mappings to a controller class or individual action, so the HTTP concern lives next to the code that throws, not in a global handler. Per-class defaults, per-method overrides, and a fallback handler for anything unmatched. Internally it's an event listener on Symfony's exception event that inspects the target controller and dispatches to the mapped handler.

Day-to-day workflow (Symfony):

  • Map at the boundary. Annotate the controller action with the domain exceptions it can raise and the status codes/response bodies they should produce. Keep the handler small — status code, error code string, safe message.
  • Keep domain exceptions HTTP-free. Don't put Response objects or status codes inside your domain layer; let the mapping layer translate. That separation is the main thing the bundle buys you over scattered try/catch.
  • Use the fallback for the catch-all. Configure one fallback handler for unmatched exceptions that logs the real exception with a correlation ID and returns a generic 500 body. This gives you a single place to verify "unhandled" behaviour.
  • Test at the HTTP layer. Feature/functional tests that hit the endpoint and assert the status + body shape, not unit tests on the handler. The value is in the wiring, so test the wiring.

Porting the pattern to Laravel. If you've read the source and want the behaviour, replicate it with native hooks:

// app/Exceptions/Handler.php
public function register()
{
    $this->renderable(function (OrderNotFoundException $e, $request) {
        return $request->expectsJson()
            ? response()->json(['error' => 'order_not_found'], 404)
            : back()->with('error', 'Order not found');
    });

    $this->renderable(function (Throwable $e, $request) {
        report($e); // let your existing reporter see it
        return response()->json(['error' => 'server_error'], 500);
    });
}

Layer reportable() for the collection side (log channel, queue job, Sentry) and you've reproduced the bundle's surface area with framework-native, upgrade-safe code. If your exception classes are yours, Laravel also lets each exception define its own render() — that's the closest analogue to the annotation approach and keeps errors close to their definition.

Integration tips regardless of framework:

  • Return a consistent JSON envelope (error, code, optional meta) so clients don't branch on shape.
  • Give every fallback-captured exception a correlation ID and log it — otherwise you've converted a 500 into a blind 500.
  • Keep the handler pure and allocation-light; exception-handling code runs when the app is already unhealthy.

Gotchas and Tips

Framework fit is the headline gotcha. Symfony bundles register through Kernel::registerBundles() and the DI extension lifecycle, none of which executes under Laravel's service provider boot. The advertised capability relies on Symfony's kernel.exception event, which has no direct analogue in Laravel's handler pipeline. Any adoption on Laravel is a rewrite, not a configuration step. Budget it as such or skip it.

Licensing blocks production use. The license reads NOASSERTION. That is not a grant to use, modify, or redistribute. Do not put this on a production path or copy code out of it until someone with authority resolves the terms — for a repo this inactive, that resolution may never come.

Zero adoption means you own every bug. No upstream, no issue history, no CVE path, no one watching the code that runs on every request. The exception path is exactly where you least want an unmaintained dependency.

Recursive failure is the classic trap. If a handler throws while handling, you can mask the original exception or loop. Wrap handler execution in a try/catch that degrades silently to the framework's native handler. This is the single most important thing to verify when testing.

Silent swallowing looks like stability. A catch-all that converts everything to a tidy response and never reports it will make your error dashboards go quiet. That's the worst outcome — it looks like improvement while regressions leak. Always report before responding.

PII and secrets land in exception messages. Request bodies, headers, and cookies routinely end up in traces and messages. Decide scrubbing and retention before you persist anything; the bundle does not solve this for you.

Config quirks to check. Find the config alias in Configuration.php and confirm it with bin/console debug:config <alias>. Verify precedence between class-level and method-level mappings by throwing the same exception from both — don't assume method wins. Confirm the listener is actually tagged and registered; a bundle that installs cleanly can still be functionally inert if the compiler pass doesn't run.

Composer resolution drift. Abandoned bundles pin old Symfony/PHP constraints and quietly freeze your upgrade path. If you vendor a fork, modernise it for PHP 8.2+ first — check for create_function, each(), and other removed constructs before assuming it runs.

Debugging tip. Temporarily dump the resolved handler inside the listener (dd-equivalent or dump + die) on a single route to confirm which mapping fired. Far faster than guessing from the response body.

Extension points that actually matter. The fallback handler and per-class overrides are the two surfaces worth configuring. Everything else is small enough to read in under an hour — which is the honest case for just re-implementing the pattern natively rather than inheriting the dependency.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
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