phphd / exception-handler-bundle
消息总线异常处理器
Requires
- php: >=8.1
- symfony/dependency-injection: ^6.0 | ^7.0
- symfony/http-kernel: ^6.0 | ^7.0
- symfony/messenger: ^6.2 | ^7.0
- symfony/string: ^6.0 | ^7.0
Requires (Dev)
- nyholm/symfony-bundle-test: ^3.0
- phpat/phpat: ^0.10.15
- phphd/coding-standard: ~0.5.0
- phpstan/phpstan: ^1.10.60
- phpstan/phpstan-phpunit: ^1.3.16
- phpunit/phpunit: ^10.5.13
- psalm/plugin-phpunit: ^0.18.4
- tomasvotruba/type-coverage: ^0.2.3
- vimeo/psalm: ^5.23.1
README
🧰 提供针对异常处理的定制化 Symfony Messenger 中间件。您可以轻松地重新抛出异常、链式调用或使用专用总线进行处理。
安装 📦
-
使用 composer 安装
composer require phphd/exception-handler-bundle
-
在
bundles.php中启用此扩展包PhPhD\ExceptionHandlerBundle\PhdExceptionHandlerBundle::class => ['all' => true],
配置 ⚒️
要利用此扩展包的功能,您应将 phd_exception_handler 中间件添加到列表中
framework:
messenger:
buses:
command.bus:
default_middleware: false
middleware:
+ - phd_exception_handler
- validation
- doctrine_transaction
异常处理的核心原则是将异常分发给相应的总线进行处理。每个原始总线必须有一个异常总线。
异常总线命名约定很简单:将 command.bus 的异常转发到 command.exception.bus。
framework: messenger: buses: command.exception.bus: default_middleware: false middleware: - phd_exception_rethrow_unhandled - phd_exception_chaining - phd_exception_result_filter - handle_message: - true
目前,提供的异常处理中间件不多。
重新抛出未处理的异常
中间件:phd_exception_rethrow_unhandled
如果派发的异常未得到处理,则会重新抛出。如果处理程序返回一个响应或抛出另一个异常,则认为异常已处理。
异常链式调用
中间件:phd_exception_chaining
使用 #[RaiseAs] 属性实现自动异常升级逻辑。
结果过滤器
中间件:phd_exception_result_filter
过滤掉异常处理程序的所有 null 结果。
使用 🚀
重新抛出异常
最简单的用例是在您的异常类上定义 #[RaiseAs] 属性
use PhPhD\ExceptionHandler\Chain\Escalator\RaiseAs; #[RaiseAs(AccessDeniedHttpException::class, bus: 'api.exception.bus')] final class NonWhiteListedUserException extends DomainException { }
在这个例子中,每次从底层处理程序抛出 NonWhiteListedUserException 时,它都会被提升为 AccessDeniedHttpException。
如您所见,需要指定一个属性总线选项。由于某些异常可能来自多个不同的上下文(因此,不同的总线),因此必须显式指定特定异常必须从哪个总线中提升,以便在其他场景中可以提升其他异常。
use PhPhD\ExceptionHandler\Chain\Escalator\RaiseAs; #[RaiseAs(ImportLockedHttpException::class, bus: 'api.exception.bus')] #[RaiseAs(RecoverableMessageHandlingException::class, bus: 'consumer.exception.bus')] final class ImportLockedException extends RuntimeException { }
在这个例子中,ImportLockedException 可能来自 http 上下文(api.bus)或 mq 消费者上下文(consumer.bus)。因此,提升的异常是不同的。
手动处理
异常被派发到您的自定义处理程序,在那里您可以返回一个响应、抛出一个新的异常或只是记录它并返回 null,这样异常就会再次被抛出。
#[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; } }
如果您希望为多个异常总线使用相同的异常处理程序,可以通过添加多个 #[AsMessageHandler] 属性来实现。
#[AsMessageHandler(bus: 'command.exception.bus')] #[AsMessageHandler(bus: 'query.exception.bus')] final readonly class InventoryExceptionHandler { // ... }