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 Transformer Laravel Package

riper/exception-transformer

Exception Transformer for Laravel turns thrown exceptions into consistent, structured responses. Map exception types to custom transformers to control status codes, messages, and error payloads, so API error output stays predictable and your exception handlers stay clean.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a tiny, low-traffic bundle (0 stars; latest release is v2.0.0, a maintenance drop that only bumps the compatibility floor to PHP 7.4+ and Symfony 4+). Treat it as a reference implementation more than a dependency you build a platform on. The core idea is: register one or more "transformers" that map a low-level exception (PDO, Guzzle, Doctrine, a vendor SDK exception) into a domain exception your app already understands, and do it before kernel.exception listeners and the error controller run.

Minimal steps:

  1. composer require riper/exception-transformer:^2.0
  2. Register the bundle:
    • Symfony 4+: add Riper\ExceptionTransformerBundle\ExceptionTransformerBundle::class => ['all' => true] to config/bundles.php.
  3. Read, in this order, before writing code:
    • the TransformerInterface (typically a supports(\Throwable): bool + transform(\Throwable): \Throwable pair),
    • the service tagging convention the bundle uses to collect transformers (look in the bundle's DependencyInjection/ for the tag name, e.g. exception_transformer.transformer),
    • the kernel listener it registers and its priority.
  4. Write your first transformer and tag it:
final class PdoToDomainTransformer implements TransformerInterface
{
    public function supports(\Throwable $e): bool
    {
        return $e instanceof \PDOException
            && str_contains($e->getMessage(), 'Duplicate entry');
    }

    public function transform(\Throwable $e): \Throwable
    {
        return new EmailAlreadyUsedException($e->getMessage(), 0, $e);
    }
}

First use case that pays for itself immediately: stop leaking PDOException / UniqueConstraintViolationException into your kernel.exception listener and API responses. Transform once, and every existing listener, ProblemNormalizer, Sentry filter, and ExceptionSubscriber sees the domain type.

Implementation Patterns

One transformer per vendor boundary. Keep supports() narrow (class check + message/code check). A transformer that matches "any \Throwable" will swallow everything and make debugging miserable.

Always chain the previous exception. new MyDomainException($msg, 0, $e) — otherwise you lose the original stack and the bundle's transformation becomes a bug-hiding machine.

Order transformers by specificity. If the bundle supports multiple matches and priority, register the most specific ones first (or rely on tag priority). The generic fallback (e.g. "any Doctrine\DBAL\Exception → PersistenceException") should run last.

Pair with a single kernel.exception listener that maps domain exceptions to HTTP status codes. The transformer gives you clean types; your listener stays a plain match over those types and never touches PDOException. This is the real day-to-day leverage: HTTP concerns stay in the listener, persistence concerns stay in the transformer.

Use it at the boundaries you don't control, not for exceptions you throw yourself. Good candidates: DBAL, Redis/PhpRedis, HTTP clients (GuzzleHttp\Exception\ConnectException), filesystem (Symfony\Component\Filesystem\Exception\IOException), messenger transport exceptions. Bad candidates: your own service-layer exceptions — just throw the right one.

Test transformers in isolation. They're pure functions; unit-test supports() and transform() directly. Add one integration test that hits a controller throwing a PDOException and asserts the JSON error body / status — this catches the "listener priority is wrong and my transformer never ran" class of bug.

Console and Messenger are separate paths. The bundle hooks the HTTP kernel, so bin/console and messenger:consume failing jobs won't go through it. If you need symmetry, wrap the same transformers in a Messenger MessageFailedEvent listener or a console error listener yourself.

Gotchas and Tips

  • Priority is everything. The listener must run before your kernel.exception subscribers. If your exception listener is registered at default priority and the bundle at default priority, order is registration-dependent and can silently flip between environments. Explicitly set priorities on your own listeners and verify with php bin/console debug:event-dispatcher kernel.exception.
  • setThrowable() semantics. The kernel event lets listeners swap the exception. Anything that captured the exception before this listener (e.g. a monolog FingersCrossed handler attached earlier, a kernel.terminate logger) sees the original. Expect log/Sentry entries that don't match your HTTP response — annotate the transformed exception with a previous chain so it's traceable.
  • Infinite transformation loops. If a transform() returns something another transformer also supports(), you can bounce between them. The bundle likely applies transformers once; don't rely on it — make returned domain exceptions not satisfy any supports() you own.
  • Don't transform \Error subtypes (TypeError, Error) into 4xx domain exceptions. They're bugs; leave them as 500s.
  • v2.0.0 is a floor bump, not a feature release. The only changes are: PHP <7.4 dropped, Symfony <4 dropped, CI moved to GitHub Actions. If you're upgrading from v1, this is a BC break — pin ^2.0 deliberately. Nothing in the transformer/listener contract changed, so your transformers and tests should port unchanged; just make sure CI runs on PHP ≥7.4.
  • Still no Symfony 6/7 claim. The release notes say "Compatible Symfony 4" — that is not the same as "compatible with a current Laravel app pulling Symfony 6/7 components." Expect deprecations or outright incompatibility around ContainerBuilder autoconfiguration and KernelInterface signatures. Verify in a scratch project before committing; you may be patching a 40-line listener.
  • Abandonment risk is reduced, not eliminated. v2.0.0 + CI migration shows the maintainer is still around, but this is housekeeping: no new features, no Symfony 6/7 target, zero downstream signal. The entire bundle is a tagged-listener + priority trick; if the next Symfony major blocks you, reimplement it in your own EventSubscriber on KernelEvents::EXCEPTION at priority 256 — that's the whole feature.
  • Debugging tip: when a transformer "doesn't fire", check in this order — (1) is the bundle registered, (2) is the tag name correct and did the compiler pass pick it up (debug:container --tag=<tag>), (3) does supports() actually match (log the class + message), (4) is a higher-priority listener setThrowable()-ing first.
  • Extension point: the transformer registry is the seam. If you need conditional behavior per environment, decorate your transformers with a TraceableTransformer (collects which transformer fired into the profiler) rather than editing the bundle — you'll want that data the first time a production error returns an unexpected status code.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package