halloverden/symfony-http-exceptions-bundle
A Symfony bundle that provides HTTP-aware exception classes for your applications. Throw typed exceptions that map to status codes and render consistent error responses while keeping controllers and services clean. Integrates with Symfony's exception handling.
This is a Symfony bundle, not a Laravel package. If you are coming from Laravel, map the concepts: it is roughly the Symfony equivalent of abort(404) / HttpException, but with dedicated exception classes and a bundle-provided exception listener for rendering consistent HTTP error responses.
Minimal setup:
Require the bundle and its underlying exception library:
composer require halloverden/symfony-http-exceptions-bundle
Register the bundle if Flex does not do it automatically:
// config/bundles.php
return [
// ...
Halloverden\SymfonyHttpExceptionsBundle\SymfonyHttpExceptionsBundle::class => ['all' => true],
];
Look first at:
vendor/halloverden/symfony-http-exceptions-bundle/README.mdvendor/halloverden/symfony-http-exceptions-bundle/src/DependencyInjection/vendor/halloverden/symfony-http-exceptions-bundle/src/Resources/config/vendor/halloverden/symfony-http-exceptions/src/ for the actual exception classesFirst use case: stop building error Response objects inside controllers. Throw a typed exception and let Symfony’s kernel.exception flow handle it:
use Halloverden\SymfonyHttpExceptions\Exception\NotFoundException;
#[Route('/users/{id}', methods: ['GET'])]
public function show(int $id): Response
{
$user = $this->users->find($id);
if ($user === null) {
throw new NotFoundException('User not found.');
}
return $this->json($user);
}
The payoff is consistency: every 404, 422, 429, etc. is raised the same way, carries the correct status code, and can be rendered through one listener instead of ad-hoc controller code.
Throw at the HTTP boundary, map in the domain. Keep domain/services free of HTTP exceptions. Throw domain exceptions internally, then convert them in a controller, a Symfony event subscriber, or a dedicated mapper:
try {
$this->orders->place($command);
} catch (OrderAlreadyPlaced $e) {
throw new ConflictException('Order already placed.', previous: $e);
}
Use specific exception classes for API semantics.
throw new BadRequestException('Malformed JSON body.');
throw new UnauthorizedException('Missing or invalid token.');
throw new ForbiddenException('You cannot access this tenant.');
throw new UnprocessableEntityException('Validation failed.', errors: $violations);
throw new TooManyRequestsException('Rate limit exceeded.', retryAfter: 60);
Centralize rendering with a kernel.exception listener. If the bundle ships a listener, confirm its priority and whether it only handles HttpExceptionInterface. If you need custom payloads, decorate or replace the bundle listener rather than adding a second one that competes with it:
final class ApiExceptionListener
{
public function onKernelException(ExceptionEvent $event): void
{
$e = $event->getThrowable();
if (!$e instanceof HttpExceptionInterface) {
return;
}
$event->setResponse(new JsonResponse([
'error' => $e->getMessage(),
'status' => $e->getStatusCode(),
], $e->getStatusCode(), $e->getHeaders()));
}
}
Validation integration. Convert Symfony Validator failures into a 422 exception once, in a shared trait or subscriber:
if (count($violations) > 0) {
throw new UnprocessableEntityException(
'Validation failed.',
errors: iterator_to_array($violations)
);
}
Reusable API controllers. In an AbstractApiController, provide helpers:
protected function notFound(string $message = 'Not found.'): never
{
throw new NotFoundException($message);
}
This keeps controller actions readable and avoids repeating status codes.
Testing pattern. Use KernelBrowser and assert both status and content type:
$client->request('GET', '/users/999');
self::assertResponseStatusCodeSame(404);
self::assertResponseHeaderSame('content-type', 'application/problem+json');
Configuration workflow. Check for a bundle config file such as config/packages/halloverden_http_exceptions.yaml. Typical knobs in bundles of this shape are whether to render JSON, whether to include exception messages/traces in debug, and which listener is enabled. Start with defaults, then override only the response format your API contract needs.
Laravel migration note. Laravel developers often reach for abort() or HttpException inside controllers. In Symfony, throwing the bundle’s exception classes is the equivalent, but the rendering path is the kernel.exception event. Do not return new Response('Not found', 404) from controllers if the goal is consistent error handling.
config/bundles.php; an unregistered bundle produces confusing “class not found” or “service not found” errors.Symfony\Component\HttpKernel\Exception\NotFoundHttpException. Importing the wrong NotFoundException is the most likely first mistake. Use the package’s namespace consistently and alias if both are needed.ErrorListener and any Twig error renderer also handle exceptions. If you get HTML instead of JSON, inspect debug:event-dispatcher kernel.exception and adjust your listener priority so the API listener runs first for API routes.$e->getMessage() or traces for 5xx responses in production. Gate detailed output behind %kernel.debug% or an explicit config flag.error, expected 4xx responses will flood logs. Configure Monolog to log HttpExceptionInterface at info/notice and reserve error/critical for 5xx.Retry-After; for 401 include WWW-Authenticate. Confirm the package’s exception constructors accept headers, or set them in your listener.Accept / route defaults before forcing JSON, or scope the listener to routes under /api.composer.json and review the source before adopting. Small bundles can become unmaintained; keep a fork path open.php bin/console debug:container --tag=kernel.event_subscriber and php bin/console debug:event-dispatcher kernel.exception quickly reveal whether the bundle wired anything at all.How can I help you explore Laravel packages today?