m6web/api-exception-bundle
Symfony bundle that centralizes API exception handling and returns clean JSON responses. Supports configurable error formatting, stack traces, form validation errors, exception matching/mapping, logging, and environment-specific behavior.
Installation (Symfony2 only)
composer require m6web/api-exception-bundle
For Laravel: Skip this. Instead, manually create equivalent exception classes (see Implementation Patterns).
First Use Case: Basic Exception Handling
config.yml to map exceptions to JSON responses (see Variables).App\Exceptions\Handler to return JSON for all exceptions:
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class Handler extends ExceptionHandler {
public function render($request, Throwable $exception) {
if ($exception instanceof HttpExceptionInterface) {
return response()->json([
'error' => $exception->getMessage(),
'code' => $exception->getStatusCode(),
'status' => 'error'
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
}
Where to Look First
Resources/doc/usage.md (exception mapping) and Resources/doc/errors.md (custom error types).app/Exceptions/Handler.php (global exception handling).app/Http/Controllers/Controller.php (for controller-specific logic).Standardized Error Responses
api_exception configuration to define error structures:
# config.yml
api_exception:
default:
code: 0
message: "An error occurred"
status: 500
// app/Traits/ApiResponder.php
trait ApiResponder {
protected function errorResponse($message, $code = 400, $errors = null) {
return response()->json([
'error' => [
'message' => $message,
'code' => $code,
'details' => $errors,
'status' => 'error'
]
], $code);
}
}
Validation Errors
ValidationException and configure in api_exception.match:
api_exception:
match:
Symfony\Component\Validator\Exception\ConstraintViolationListException: validation
// In a controller
$validator = Validator::make($request->all(), [
'email' => 'required|email',
]);
if ($validator->fails()) {
return $this->errorResponse('Validation failed', 422, $validator->errors());
}
Custom Exceptions
ApiException:
use M6Web\ApiExceptionBundle\Exception\ApiException;
class UserNotFoundException extends ApiException {
public function __construct() {
parent::__construct('User not found', 404);
}
}
Exception and implement toJson():
class UserNotFoundException extends Exception {
public function toJson() {
return response()->json([
'error' => 'User not found',
'code' => 404,
'status' => 'error'
], 404);
}
}
Stack Trace Control
api_exception:
debug: "%kernel.debug%" # Shows stack trace in dev
// In Handler.php
public function render($request, Throwable $exception) {
$response = response()->json([
'error' => $exception->getMessage(),
'code' => $exception->getCode(),
'trace' => app()->environment('local') ? $exception->getTrace() : null
]);
return $response;
}
API Development Workflow
UserNotFoundException).Handler.php or controller.errorResponse() trait or response()->json().Debugging Workflow
app()->environment('local') to conditionally include traces:
$response->setData([
'error' => $exception->getMessage(),
'trace' => app()->isLocal() ? $exception->getTrace() : null
]);
Testing Workflow
ApiExceptionBundle\Tests as a reference.public function test_user_not_found() {
$exception = new UserNotFoundException();
$response = $this->getExceptionResponse($exception);
$response->assertStatus(404);
$response->assertJson([
'error' => 'User not found',
'code' => 404
]);
}
Laravel-Symfony Bridge
symfony/http-foundation for shared components:
composer require symfony/http-foundation
JsonResponse to Laravel’s response:
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Http\JsonResponse as LaravelJsonResponse;
$symfonyResponse = new JsonResponse(['error' => 'test']);
$laravelResponse = new LaravelJsonResponse(
$symfonyResponse->getContent(),
$symfonyResponse->getStatusCode()
);
Event-Driven Extensions
api_exception.event to listen for exception events.Handler.php:
use Illuminate\Support\Facades\Event;
public function report(Throwable $exception) {
Event::dispatch(new ExceptionOccurred($exception));
}
Configuration Management
config.yml.config/exceptions.php:
return [
'default' => [
'code' => 0,
'message' => 'An error occurred',
'status' => 500,
],
'match' => [
'App\Exceptions\UserNotFoundException' => 404,
],
];
Symfony Dependency Hell
HttpKernel, EventDispatcher).Configuration Overrides
spatie/laravel-config-array.Stack Trace Exposure
'trace' => app()->environment('local') ? $exception->getTrace() : null
Validation Error Formatting
ConstraintViolationListException format differs from Laravel’s validator errors.$errors = collect($validator->errors()->all())
->map(fn($error) => ['field' => 'email', 'message' => $error]);
Middleware vs. Handler Conflicts
Handler.php process errors.return parent::render() in Handler.php to avoid duplication.Symfony2-Specific Issues
bin/console debug:exception to inspect mapped exceptions.Handler.php:
Log::error($exception->getMessage(), [
'trace' => $exception->getTrace(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
]);
Custom Exception Not Triggering
How can I help you explore Laravel packages today?