https://github.com/phphd/exception-handler
Bundle implementing exception handler via Symfony Messenger component
https://github.com/phphd/exception-handler
Last synced: 5 months ago
JSON representation
Bundle implementing exception handler via Symfony Messenger component
- Host: GitHub
- URL: https://github.com/phphd/exception-handler
- Owner: phphd
- License: mit
- Created: 2024-02-20T12:09:46.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2025-01-11T19:37:11.000Z (over 1 year ago)
- Last Synced: 2025-11-18T06:24:17.418Z (8 months ago)
- Language: PHP
- Homepage:
- Size: 35.2 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Exception Handler
🧰 PHP Exception Handler built on top of [Symfony Messenger](https://symfony.com/doc/current/messenger.html) component.
Provides middlewares that allow you to easily re-raise exceptions, chain them, or handle with a dedicated handler.
[](https://github.com/phphd/exception-handler/actions?query=branch%3Amain)
[](https://codecov.io/gh/phphd/exception-handler)
[](https://shepherd.dev/github/phphd/exception-handler)
[](https://shepherd.dev/github/phphd/exception-handler)
[](https://packagist.org/packages/phphd/exception-handler)
[](https://github.com/phphd/exception-handler/blob/main/LICENSE)
## Installation 📥
1. Install via composer
```sh
composer require phphd/exception-handler
```
2. Enable the bundle in the `bundles.php`
```php
PhPhD\ExceptionHandler\Bundle\PhdExceptionHandlerBundle::class => ['all' => true],
```
## Configuration ⚒️
To leverage features of this bundle, you should add `phd_exception_handler` middleware to the list:
```diff
framework:
messenger:
buses:
command.bus:
default_middleware: false
middleware:
+ - phd_exception_handler
- validation
- doctrine_transaction
```
The core principle of exception handling revolves around the idea that exceptions are dispatched to the corresponding
bus to be handled. There must be one exception bus per one origin bus.
The exception bus name convention is straightforward: `command.bus` exceptions are forwarded
into `command.exception.bus`.
```yaml
framework:
messenger:
buses:
command.exception.bus:
default_middleware: false
middleware:
- phd_exception_rethrow_unhandled
- phd_exception_chaining
- phd_exception_result_filter
- handle_message:
- true
```
Currently, there are few exception handling middlewares provided.
### Rethrowing unhandled
Middleware: `phd_exception_rethrow_unhandled`
In case if dispatched exception had not been handled it is rethrown back. The exception is considered as handled if
handler returns a response, or throws another exception.
### Exception chaining
Middleware: `phd_exception_chaining`
Implements automatic exceptions escalation logic with `#[RaiseAs]` attribute.
### Result filter
Middleware: `phd_exception_result_filter`
Filters out all null results of exception handlers.
## Usage 🚀
### Re-Raising Exceptions
The simplest use-case is defining `#[RaiseAs]` attribute on your exception class:
```php
use PhPhD\ExceptionHandler\Middleware\Chain\Escalator\RaiseAs;
#[RaiseAs(AccessDeniedHttpException::class, bus: 'api.exception.bus')]
final class NonWhiteListedUserException extends DomainException
{
}
```
In this example, any time `NonWhiteListedUserException` is thrown from an underlying handler,
it will be raised as `AccessDeniedHttpException`.
As you can see, there's required attribute bus option. Since some exceptions could be thrown from multiple different
contexts (hence, different buses), it is required to explicitly specify the bus from which the particular exception
must be raised, so that in other scenarios another exceptions could be escalated:
```php
use PhPhD\ExceptionHandler\Middleware\Chain\Escalator\RaiseAs;
#[RaiseAs(ImportLockedHttpException::class, bus: 'api.exception.bus')]
#[RaiseAs(RecoverableMessageHandlingException::class, bus: 'consumer.exception.bus')]
final class ImportLockedException extends RuntimeException
{
}
```
In this example, `ImportLockedException` could be thrown either in http context (`api.bus`), or in the mq consumer
context (`consumer.bus`). Therefore, raised exceptions are different.
### Manual Handling
The exception is dispatched down to your custom handlers, where you could either return a Response, throw a new
exception, or just log it and return `null` so that exception will be re-thrown again.
```php
#[AsMessageHandler('api.exception.bus')]
final readonly class InventoryExceptionHandler
{
/** @throws Throwable */
public function __invoke(InventoryDomainException $exception, InventoryCommand $command): ?Response
{
if ($exception instanceof JournalHasUnInventoriedItemException) {
$data = $this->formatJournalException($exception);
return new JsonResponse($data, Response::HTTP_BAD_REQUEST);
}
if ($exception instanceof StockItemNotVaildatedException) {
$data = $this->formatItemException($exception, $command->getJournal());
throw new StockItemNotValidatedHttpException($data, $exception);
}
return null;
}
}
```
If you would like to use the same exception handler for multiple exception buses, you can do so by adding multiple
`#[AsMessageHandler]` attributes:
```php
#[AsMessageHandler(bus: 'command.exception.bus')]
#[AsMessageHandler(bus: 'query.exception.bus')]
final readonly class InventoryExceptionHandler
{
// ...
}
```