hradigital / php-exceptions-laravel
Laravel wrapper for hradigital/php-exceptions, wiring Laravel's error handling and providing a JSON Renderer for these Exceptions.
Package info
github.com/HRADigital/php-exceptions-laravel
pkg:composer/hradigital/php-exceptions-laravel
Requires
- php: ^8.1
- hradigital/php-exceptions: ^1.0
- illuminate/http: ^10.0 || ^11.0 || ^12.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0
- symfony/http-foundation: ^6.0 || ^7.0
Requires (Dev)
- orchestra/testbench: ^8.0 || ^9.0 || ^10.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0 || ^11.0
- squizlabs/php_codesniffer: ^3.7
This package is auto-updated.
Last update: 2026-05-05 16:47:44 UTC
README
Laravel wiring + JSON renderer for hradigital/php-exceptions.
The base library ships platform-agnostic domain exceptions (AbstractBaseException and the Client/+Server/ trees, aligned with HTTP 4xx/5xx semantics). This package translates them into a uniform JSON response at the HTTP boundary and auto-registers itself with Laravel's exception handler, but only for API requests — browser requests still fall through to Laravel's default error pages.
| Package | hradigital/php-exceptions-laravel |
|---|---|
| Namespace | HraDigital\Components\ExceptionRenderer |
| Requires | PHP ^8.1, hradigital/php-exceptions ^1.0 |
| Laravel | ^10.0 · ^11.0 · ^12.0 |
| License | GPL-3.0-or-later |
Installation
composer require hradigital/php-exceptions-laravel
Registration
The service provider is auto-discovered — no manual registration needed in most apps. If auto-discovery is disabled for this package, register it explicitly:
Laravel 11 / 12 — bootstrap/providers.php:
return [ HraDigital\Components\ExceptionRenderer\ExceptionsServiceProvider::class, ];
Laravel 10 (and below) — config/app.php:
'providers' => ServiceProvider::defaultProviders()->merge([ HraDigital\Components\ExceptionRenderer\ExceptionsServiceProvider::class, ])->toArray(),
What the provider does
On boot(), the provider:
- Binds
ExceptionRendereras a singleton in the container (so app code can resolve it and add custom strategies). - Hooks into Laravel's exception handler via
renderable(), intercepting any thrownAbstractBaseException.
The hook only renders when the incoming request is treated as an API request:
| Signal | Detected via |
|---|---|
Accept header negotiates JSON |
$request->expectsJson() / wantsJson() |
| Request body is JSON | $request->isJson() |
URL path matches api/* (or is exactly api) |
$request->is('api/*') |
Matched route name starts with api. |
$request->route()?->getName() |
For any other request, the callback returns null, so Laravel's default handler keeps rendering its usual HTML / Whoops / Blade error pages.
Response shapes
Default
Any AbstractBaseException rendered via DefaultRenderer:
{
"message": "Resource not found.",
"code": 404,
"data": { "id": 42 }
}
data is omitted when the exception was raised without structured payload (hasData() === false).
Input / validation failures
Exceptions implementing HraDigital\Components\Exceptions\Client\Request\RequestFailureInterface are rendered via InputFailureRenderer:
{
"message": "Invalid input.",
"code": 422,
"rules": { "email": ["required"] },
"failed": [
{ "fieldName": "email", "message": "email is required" }
]
}
rules mirrors getFailures() (the field-keyed rule list). failed is the flattened, per-message list derived from getFailedMessages() — one entry per {fieldName, message} pair, preserving field order.
Adding a custom renderer strategy
Resolve the singleton and prepend a strategy. The first strategy whose supports() returns true wins; the bundled DefaultRenderer is the always-matching fallback.
use HraDigital\Components\ExceptionRenderer\ExceptionRenderer; use HraDigital\Components\ExceptionRenderer\Renderers\ExceptionRendererInterface; use HraDigital\Components\Exceptions\AbstractBaseException; use Symfony\Component\HttpFoundation\JsonResponse; final class TenantQuotaRenderer implements ExceptionRendererInterface { public function supports(AbstractBaseException $exception): bool { return $exception instanceof \App\Exceptions\TenantQuotaExceeded; } public function renderAsJson(AbstractBaseException $exception): JsonResponse { return new JsonResponse([ 'message' => $exception->getMessage(), 'code' => $exception->getCode(), 'tenant' => $exception->getData()['tenant'] ?? null, ], $exception->getCode()); } } // In a service provider's boot(): app(ExceptionRenderer::class)->add(new TenantQuotaRenderer());
add() always prepends, so later registrations override earlier ones.
Public API
| Class / interface | Purpose |
|---|---|
ExceptionRenderer |
Strategy dispatcher; renderAsJson(), add(), getStrategies(), factory. |
Renderers\ExceptionRendererInterface |
Contract every strategy implements (supports() + renderAsJson()). |
Renderers\DefaultRenderer |
Always-matching fallback; emits the default response shape. |
Renderers\InputFailureRenderer |
Matches RequestFailureInterface; emits validation-failure shape. |
ExceptionsServiceProvider |
Singleton binding + Laravel renderable() hook with API-request gating. |
Local development
composer install composer ci # lint + phpcs + phpstan + phpunit composer test # phpunit only composer cs # PSR-12 check composer cs:fix # PSR-12 autofix composer stan # phpstan (level 6)
Continuous Integration
.github/workflows/ci.yml runs on every push and PR to master. It executes lint → phpcs → phpstan → phpunit against the supported PHP × Laravel matrix:
| Laravel 10 | Laravel 11 | Laravel 12 | |
|---|---|---|---|
| 8.1 | ✓ | ||
| 8.2 | ✓ | ✓ | ✓ |
| 8.3 | ✓ | ✓ | |
| 8.4 | ✓ |
The workflow pins illuminate/* and orchestra/testbench per matrix cell with composer require --no-update before installing.
Testing
The tests/ suite covers every class in the package:
ExceptionRendererTest— factory wiring, fallback path, strategy routing, prepend ordering, custom-constructor wiring.Renderers/DefaultRendererTest— status/message/code mapping, conditionaldatakey.Renderers/InputFailureRendererTest— flattening offailed,rulesmirroring, empty-payload behaviour.ExceptionsServiceProviderTest— Testbench-based: singleton binding, end-to-end HTTP rendering for JSON /api/*requests, and direct callback invocation proving non-API requests pass through (nullreturn).Support/Stubs— shared anonymous-class factories for a generic exception and aRequestFailureInterfaceexception, excluded from the test suite.
License
GPL-3.0-or-later — see LICENSE. This matches the upstream hradigital/php-exceptions license.