samson/unexpected-response-bundle
A Symfony bundle for handling unexpected HTTP responses, helping applications detect, catch, and present responses that fall outside normal expectations with a structured, consistent approach.
This is a Symfony 2-era bundle, not a Laravel package — treat it as a reference implementation of a pattern rather than a dependency you install into a Laravel app.
If you are evaluating it inside a Symfony project:
composer require "samson/unexpected-response-bundle:^1.0"
app/AppKernel.php (Symfony 2/3 style — there is no Flex recipe, no bundles.php auto-registration, and no service autowiring).vendor/samson/unexpected-response-bundle/:
Response payload,kernel.exception event listener that unwraps it and returns the response.
Those two files are the package; everything else is boilerplate.First use case — the canonical one it was built for: you are several layers deep in a service (an authentication guard, a rate limiter, a domain invariant check) and need to short-circuit with a redirect or an error response, but you only have a return value to work with. Instead of threading a Response back up through five method signatures, you throw:
throw new UnexpectedResponse(new RedirectResponse('/login'));
The exception listener catches it before Symfony's default error rendering and returns the wrapped response as-is.
If you are a Laravel developer, read the two source files above and then stop — see the Gotchas section for the Laravel equivalent, which you already have in core.
Short-circuit from a domain service. The main value is avoiding Response objects leaking into your service layer signatures. A service method keeps returning its normal type; on the failure path it throws the response-carrying exception. Callers stay clean.
Guards and filters. Auth checks, feature flags, and maintenance-mode checks inside listeners or middleware are natural fits — anywhere you want to bail out with a redirect or 403 without unwinding a return value through the call stack.
API error envelopes. Wrap a JsonResponse with your standard error shape and throw it from a repository or query object when a record is missing or a precondition fails.
Testing. Two levels:
getResponse()->getStatusCode()).Integration tip — keep the throw site boring. Wrap the throw in a small named factory (AuthRequired::redirect(), ApiError::notFound($id)) so the rest of the codebase never touches the bundle's class directly. That single indirection is what makes the code portable when you eventually replace the bundle.
Laravel translation of the pattern. You do not need this package. Use:
abort(403) / abort(404) inside services — Laravel's HttpException is caught by the exception handler.Illuminate\Contracts\Support\Responsable, so render() returns the response you want.App\Exceptions\Handler::render() for global shaping.RedirectResponse thrown from a form request's failedAuthorization().The Symfony bundle and Laravel's Responsable/abort() are solving the same problem; Laravel's version is in core and maintained.
It is effectively abandoned. Last release August 2014, one star, zero dependents. There is no Symfony 4/5/6/7 support: the bundle predates bundles.php, autowiring, and the EventDispatcherInterface changes. Do not add it to a modern Symfony app; copy the ~40 lines of listener and exception class into your own src/ instead.
Listener priority is the whole trick. The bundle must run before Symfony's ExceptionListener (which renders the error page). If you copy the code, register at a priority higher than the default 0 — otherwise you get a 500 page and a logged exception instead of your response. This is the #1 thing to verify when porting.
Exceptions are for exceptional paths, not flow control. It is tempting to throw a redirect for every "not logged in" case in every service. That makes stack traces your debugging interface and hides control flow. Restrict it to genuinely terminal paths; ordinary validation should still return values.
Sub-requests and ESI. If the throw happens inside a sub-request, the listener still fires, but the returned response may not be what the parent expects. Test fragmented/embedded rendering explicitly if you use it.
Responses are fully-formed, not templates. Once thrown, nothing downstream runs — no kernel.response listeners that mutate your response will have run yet in the original flow, and any work after the throw site is skipped. Do not rely on finally-style cleanup unless you actually use finally.
Debug mode changes behavior. In dev with the profiler, an uncaught exception that looks handled can still surface in the toolbar. Confirm behavior with prod-like settings before concluding it works.
No config, no extension points. There is no Configuration tree, no service aliases, no way to customize the exception class. If you need an error-code-to-response mapping, that is your code, not the bundle's.
Composer friction. No dev-master stability guarantees, no composer.json branch-alias for modern constraints. A fresh composer require may resolve to a 2014 commit with no Symfony constraint, silently allowing an incompatible framework version. Pin both the bundle and Symfony explicitly and run composer why-not if it refuses to install.
Verdict for a Laravel shop: skip the dependency. Read it as a 40-line case study in "how do I return an HTTP response from a non-controller," then implement it with abort(), Responsable, and Handler::render() — all supported, all tested against current Laravel.
How can I help you explore Laravel packages today?