phphd/exception-handler-bundle

消息总线异常处理器

1.0.0 2024-08-05 17:45 UTC

This package is auto-updated.

Last update: 2024-09-05 18:03:54 UTC


README

🧰 提供针对异常处理的定制化 Symfony Messenger 中间件。您可以轻松地重新抛出异常、链式调用或使用专用总线进行处理。

Build Status Codecov Psalm coverage Psalm level Packagist Downloads Licence

安装 📦

  1. 使用 composer 安装

    composer require phphd/exception-handler-bundle
  2. 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
{
    // ...
}