orqestrahub / laravel-exceptions
Structured domain exception catalogs and runtime exception handling utilities for Laravel applications.
Requires
- php: ^8.3
- illuminate/contracts: ^12.0
- illuminate/support: ^12.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
- roave/security-advisories: dev-latest
This package is auto-updated.
Last update: 2026-08-31 15:45:05 UTC
README
Structured domain exception catalogs and runtime exception handling utilities for Laravel applications.
The package allows applications to organize errors by domain or functional area, assign stable error codes to them, and build consistent service exceptions through a centralized repository.
It is especially useful for modular and domain-oriented applications where each bounded context owns its own exception catalog.
For example:
users
├── 1000 USER_NOT_FOUND
├── 1001 USER_ACCESS_DENIED
└── 1002 USER_ALREADY_EXISTS
payments
├── 1000 PAYMENT_PROCESSING_FAILED
├── 1001 PAYMENT_NOT_FOUND
└── 1002 PAYMENT_ALREADY_PROCESSED
accounts
├── 1000 ACCOUNT_NOT_FOUND
├── 1001 ACCOUNT_BLOCKED
└── 1002 INSUFFICIENT_BALANCE
Each domain defines its own categories and error codes while the application uses a shared exception repository to register and build exceptions consistently.
Requirements
- PHP 8.3+
- Laravel 12+
Installation
Install the package using Composer:
composer require orqestrahub/laravel-exceptions
Exception Categories
Errors are grouped into categories representing a domain, module, bounded context, or functional area.
<?php use OrqestraHub\Laravel\Exceptions\Definitions\BaseExceptionCategory; final class UsersExceptionCategory extends BaseExceptionCategory { public static function getName(): string { return 'Users'; } public static function getSlug(): string { return 'users'; } }
The category slug becomes part of the final error identifier.
Exception Definitions
Each exception definition contains a numeric error code together with machine-readable and human-readable metadata.
<?php use OrqestraHub\Laravel\Exceptions\Definitions\BaseExceptionObject; final class UserNotFound extends BaseExceptionObject { public static function getErrorCode(): int { return 1000; } public static function getErrorName(): string { return 'USER_NOT_FOUND'; } public static function getErrorDesc(): ?string { return 'The requested user could not be found.'; } public static function getErrorMessage(): string|array { return 'User not found.'; } public static function getHttpCode(): int { return 404; } }
Another error in the same category can use another numeric code:
<?php use OrqestraHub\Laravel\Exceptions\Definitions\BaseExceptionObject; final class UserAccessDenied extends BaseExceptionObject { public static function getErrorCode(): int { return 1001; } public static function getErrorName(): string { return 'USER_ACCESS_DENIED'; } public static function getErrorDesc(): ?string { return 'Access to the requested user resource was denied.'; } public static function getErrorMessage(): string|array { return 'You do not have permission to access this resource.'; } public static function getHttpCode(): int { return 403; } }
The package does not enforce any specific numeric range for exception codes.
Any integer value can be used as long as it is unique within the registered exception category.
For example, all of the following are valid:
users-10
users-100
users-1000
However, starting domain error codes from 1000 is recommended as a simple and consistent convention.
For example:
users-1000
users-1001
users-1002
payments-1000
payments-1001
payments-1002
auth-1000
auth-1001
auth-1002
Different categories may use the same numeric values because the final error identifier combines:
category slug + numeric error code
For example:
users-1000
payments-1000
auth-1000
These are different error identifiers because their category slugs are different.
Registering Domain Exceptions
Each domain or module can provide its own exception registrar.
<?php use OrqestraHub\Laravel\Exceptions\Contracts\ExceptionRegistrarInterface; use OrqestraHub\Laravel\Exceptions\Services\ExceptionRepository; final class UsersExceptions implements ExceptionRegistrarInterface { public static function register(ExceptionRepository $repository): void { $category = UsersExceptionCategory::class; $repository->registerExceptionCategory($category); $repository->registerExceptions($category, [ UserNotFound::class, // error code: 1000 UserAccessDenied::class, // error code: 1001 ]); } }
This keeps exception definitions close to the domain that owns them while allowing the application to aggregate all exception catalogs into one repository.
Application Service Provider
The package provides an abstract service provider that creates the shared ExceptionRepository and registers all configured exception registrars.
Create an application service provider:
<?php declare(strict_types=1); namespace App\Providers; use OrqestraHub\Laravel\Exceptions\AbstractServiceProvider; use OrqestraHub\Laravel\Exceptions\Contracts\ExceptionRegistrarInterface; final class ExceptionServiceProvider extends AbstractServiceProvider { /** * @return array<int, class-string<ExceptionRegistrarInterface>> */ protected function getCustomRegistrars(): array { return [ UsersExceptions::class, AuthExceptions::class, PaymentsExceptions::class, ]; } }
Each registrar is executed when the exception repository is first resolved from the Laravel container.
The provider also registers the package console commands when the application is running in console mode.
Register the Provider
Laravel 12 application service providers are registered in bootstrap/providers.php.
Add your provider to the returned array:
<?php return [ App\Providers\AppServiceProvider::class, App\Providers\ExceptionServiceProvider::class, ];
After that, the exception repository can be resolved anywhere through Laravel's service container.
Building Exceptions
Resolve the exception repository from the Laravel container and build an exception using its category slug and error code.
<?php use OrqestraHub\Laravel\Exceptions\Services\ExceptionRepository; $exceptionRepository = app()->make(ExceptionRepository::class); $exception = $exceptionRepository->buildException( catSlug: UsersExceptionCategory::getSlug(), errorCode: UserNotFound::getErrorCode(), customMessage: 'The requested user does not exist.', customHttpCode: 404, previous: null, loggerScope: 'users-service', ); throw $exception;
The registered exception definition provides the default metadata while runtime values such as the message, HTTP status code, previous exception, or logger scope can be supplied when necessary.
JSON Error Response
A service exception can be serialized into a stable JSON error contract.
For example:
{
"error_code": "payments-1100",
"error_name": "PAYMENT_PROCESSING_FAILED",
"error_desc": "The system was unable to process the payment using any available route or provider.",
"message": "The payment can't be processed."
}
The error_code combines the exception category slug with the numeric error code.
This gives API clients, integrations, logs, and other services a stable identifier that does not depend on internal PHP exception class names or exception messages.
Laravel 12 Exception Handling
Laravel 12 allows exception rendering to be configured in bootstrap/app.php.
A typical API integration can render BaseServiceException instances directly as JSON.
<?php use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; use OrqestraHub\Laravel\Exceptions\BaseServiceException; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { // }) ->withExceptions(function (Exceptions $exceptions): void { $exceptions->dontReport([ BaseServiceException::class, ]); $exceptions->render( function (BaseServiceException $exception) { return response()->json( $exception->toArray(), $exception->getHttpCode(), ); } ); }) ->create();
Applications remain responsible for deciding how validation errors, unknown exceptions, logging, and environment-specific error messages should be handled.
Console
The package registers an interactive Artisan command for browsing the configured exception catalog:
php artisan exceptions:list
The command allows you to select a registered category and inspect its exception codes, names, and default messages.
This can be useful during development, debugging, documentation work, and API integration.
Domain-Oriented Error Catalogs
The package is designed to make error ownership explicit.
A domain can define its own exception category:
Users
└── UsersExceptionCategory
├── UserNotFound
├── UserAccessDenied
└── UserAlreadyExists
while another domain can define an independent catalog:
Payments
└── PaymentsExceptionCategory
├── PaymentNotFound
├── PaymentProcessingFailed
└── PaymentAlreadyProcessed
Each category independently registers its own error definitions.
The package does not require categories to coordinate numeric ranges with each other.
For example, the following identifiers may all exist at the same time:
users-1000
payments-1000
accounts-1000
The numeric portion does not have to start from 1000, but using 1000+ for domain errors is recommended as a clear and consistent application convention.
This makes the package convenient for applications composed of multiple bounded contexts, modules, or services while keeping error definitions close to the code that owns them.
Why Use Exception Categories?
In larger applications, relying only on PHP exception class names makes it difficult to maintain stable error contracts across APIs, modules, domains, and services.
This package separates domain ownership from runtime exception handling:
Domain
↓
Exception category
↓
Exception definition
↓
Stable error identifier
↓
Runtime service exception
↓
JSON API response
For example:
Payments
↓
payments
↓
1100 PAYMENT_PROCESSING_FAILED
↓
payments-1100
↓
BaseServiceException
↓
JSON response
The result is a predictable error contract while keeping exception definitions inside the domain that owns them.
Stability
This package is used in production applications.
Automated tests have not yet been extracted into the standalone package repository. The code has been exercised and tested as part of production systems prior to its extraction into this package.
Standalone test coverage will be added incrementally.
Code Style
Check code style:
composer cs:check
Fix code style:
composer cs:fix
License
This package is open-sourced software licensed under the MIT License.