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

Gif Exception Bundle Laravel Package

jolicode/gif-exception-bundle

Symfony bundle that turns exceptions into animated GIFs. Instead of a plain error page, it displays a random GIF alongside the exception message and stack trace, making debugging and error pages more fun. Quick to install and configure in any Symfony application.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the blunt part: this is a Symfony bundle, not a Laravel package. There is no service provider, no kernel.exception event wiring for Laravel, and no config publishing. composer require --dev jolicode/gif-exception-bundle in a Laravel app will install Symfony components you already have transitively and then do exactly nothing. Treat this assessment as "what it does, whether the idea is worth stealing, and how to get the same result in Laravel."

The v1.12.0 release changes nothing about that verdict. It is Symfony-side housekeeping — Symfony 8.2 support, a PHPUnit 11 upgrade, a Castor task replacing the GIF optimizer command, a fix to exception status-code handling, extra tests for non-HTTP exceptions and disabled debug mode, and a move to the standard bundle directory structure. The relevant part for a Laravel developer is that the project is alive and maintained, which makes the pattern worth copying but the package still irrelevant to install.

For a Symfony app (or a polyglot team where you own one), the setup is genuinely minimal:

composer require --dev jolicode/gif-exception-bundle

Register it for dev only in config/bundles.php:

return [
    // ...
    JoliCode\GifExceptionBundle\GifExceptionBundle::class => ['dev' => true],
];

Then throw something from a controller and reload. Where to look first, in order:

  1. src/DependencyInjection/Configuration.php — the authoritative list of options (GIF source/provider, API key, environment gating). Note: v1.12.0 moved to the standard bundle directory structure, so the class sits under src/ rather than at the bundle root. The README drifts; the config tree doesn't.
  2. The event listener registered on kernel.exception — that's the whole trick: it intercepts the exception before Symfony's default ErrorListener renders the HTML error page, and renders its own Twig template instead. v1.12.0 added explicit tests for the debug-disabled path, so this listener now has documented, asserted behaviour when debug is off — read those tests, they're the clearest spec of the fallback.
  3. The Twig template — the extension point you'll actually touch if you want to change layout or fall back gracefully.

First realistic use case: a local dev environment where a 500 during frontend work costs you ten seconds of squinting at a stack trace. The GIF page keeps the status code and message while making the page memorable. That's the entire value proposition — it is a joke tool with a real debugging-ergonomics edge.

For Laravel, the equivalent first step is to check what you already have: Laravel 8+ ships Spatie Ignition as the default debug page, and it already supports custom tabs and "solutions." If you just want a fun page, you extend Ignition, not this bundle.

Implementation Patterns

Dev-only registration, enforced twice. In Symfony, register the bundle under ['dev' => true] in bundles.php, and keep the app's APP_ENV honest. In Laravel ports, gate on both the environment and debug flag — they are not the same thing and staging usually has one but not the other:

// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (Throwable $e, Request $request) {
        if (! app()->environment('local') || ! config('app.debug')) {
            return null; // fall through to the normal handler
        }
        if ($request->expectsJson()) {
            return null; // never break API clients with HTML
        }

        return response()->view('errors.gif', ['exception' => $e], $this->statusFor($e));
    });
});

Returning null from a render callback is the Laravel equivalent of listener priority: it says "not mine, keep going."

Preserve the exception's status code, don't hardcode 500. v1.12.0 shipped a fix titled "Fix exception code handling," which is a useful reminder: the bundle now renders the exception's own code (404, 403, 422) rather than assuming every throw is a 500. Mirror that — a ModelNotFoundException should still surface as a 404 with a GIF, not get flattened to a 500. Derive the status from the exception (via HttpExceptionInterface::getStatusCode() in Laravel, or a small mapper for domain exceptions) and only default to 500.

Steal the architecture, not just the joke. The bundle's structure is a small, clean pattern worth copying: a GifRepository-style interface with two implementations — one that resolves from a local vendored GIF set, one that calls a remote API. Bind the local one by default and the API-backed one only when a key is configured:

interface GifRepository { public function forException(Throwable $e): string; }

class LocalGifRepository implements GifRepository { /* pick from resources/gifs */ }
class RemoteGifRepository implements GifRepository { /* HTTP call + cache */ }

This is what makes the difference between a toy that works on a plane and one that hangs your error page for 2s on a flaky network.

Map the exception to the GIF deterministically. Hash the exception class name into the GIF array rather than random_int(). Reasons: the same bug shows the same GIF (useful pattern recognition during a debugging session), and functional tests that snapshot the page stay stable. Note that v1.12.0's test additions — non-HTTP exceptions, disabled debug — are exactly the kind of cases your own deterministic mapper needs to handle without throwing a second exception inside the error renderer.

Wire it as middleware-free as possible. In Laravel, exception rendering belongs in withExceptions, not in a middleware try/catch. Middleware won't catch errors thrown during container boot or in other middleware that runs before yours. The bundle gets this right by hooking the kernel event; mirror that by staying in the exception handler.

Integration tip for mixed stacks: if you run Symfony and Laravel services side by side, standardise on one "fun debug page" policy per environment. Two teams inventing two different 500 pages on the same shared staging host is how you end up debugging a debug page.

Gotchas and Tips

  • It will not work in Laravel, and it will not fail loudly. No error, no warning — just a dependency you now carry for nothing. This is the single biggest gotcha given the package has 0 dependents on the registry; almost nobody is exercising it in a Laravel context, so don't expect search results to save you. v1.12.0 does not change this — there is no Laravel-facing work in the changelog.
  • BC break: the GIF optimizer console command is gone. v1.12.0 replaced it with a Castor task. If you had it wired into a Makefile, a CI step, or onboarding docs for a Symfony app, that step now fails. It's dev tooling only, so blast radius is small, but it's the one genuinely breaking item in the release.
  • Paths moved. The standard bundle directory structure means DependencyInjection/, Resources/, and friends are now under src/ and templates/. Any bookmarked GitHub URLs, docs, or IDE breakpoints referencing the old root-level layout will 404 or point at nothing. Same code, new address.
  • Release metadata is unreliable; trust the changelog. The registry has previously listed future-dated releases. Before pinning, check the actual tag list and the v1.11.1…v1.12.0 compare diff rather than the version metadata. For a dev-only tool this is low stakes; verify anyway if you'd promote it to staging.
  • Listener priority races. In Symfony, if the plain error page still appears, your listener lost the priority battle or the bundle isn't loaded in that env. bin/console debug:event-dispatcher kernel.exception shows the exact order — do that before touching config.
  • test env is where it bites. Any functional test asserting on a 500 response body will now assert against a Twig template full of <img> tags. Either restrict the bundle to dev only (not dev + test), or assert purely on status code and headers. v1.12.0's new tests around disabled debug mode give you a model for the assertions that survive: boot with debug off, assert a plain response.
  • Network dependency on the error path. If the GIF comes from a remote API, your error page now depends on an external service with rate limits and an API key. Put the key in .env.local, never in a committed file, and always implement a local fallback so a rate-limited or offline environment still renders a usable stack trace.
  • Don't let it render in production, even behind auth. The page leaks exception messages, file paths, and stack traces by design. Environment gating is not optional, and "we'll just protect staging with basic auth" is a habit that eventually fails. Add one test that boots with APP_DEBUG=false and asserts a plain JSON 500 — cheap insurance, and now literally the scenario upstream tests.
  • Customisation is via template override, not config. In Symfony, override the bundle's Twig template under templates/bundles/…; in a Laravel port, just publish your own view. There's no rich configuration surface here, and expecting one wastes time — read Configuration.php once and move on.
  • Caching surprises. After changing bundle config in dev, bin/console cache:clear before concluding it's broken; container rebuilds don't always happen on the fly for bundle registration changes, and the v1.12.0 directory move makes stale autoload/classmap more likely than usual on upgrade.
  • Verdict for day-to-day leverage: as a Laravel developer, the direct leverage is still zero; the pattern leverage is modest but real (environment-gated exception renderer, status-code fidelity, interface + local/remote implementations, deterministic selection, fallback on the error path). v1.12.0 is a maintenance release: it raises the Symfony ceiling and hardens the test suite, but adds nothing you'd port. Spend twenty minutes reading the listener, the repository code, and the new debug-off tests, then implement your own version in bootstrap/app.php — that's a better use of time than trying to wedge a Symfony bundle into a Laravel app.
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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment