lcobucci/error-handling-middleware

兼容RFC 7807的PSR-15中间件

1.4.0 2022-02-20 20:36 UTC

README

Total Downloads Latest Stable Version Unstable Version

Build Status Code Coverage

动机

有很多PHP实现支持RFC 7807,甚至提供PSR-15中间件。然而,其中大多数——如果不是全部——将内容协商、日志记录和格式化与错误处理混合在一起。有些甚至强迫你抛出特定类型的异常才能使其工作。

我认为这不是最佳的设计决策,我们需要更多的灵活性来解决此问题。

安装

此包可在Packagist上找到,我们建议您使用Composer进行安装

composer require lcobucci/error-handling-middleware

用法

为了使用此包,您必须将中间件添加到您的管道中,配置所需的行为(调试信息策略和状态码提取策略)。

设置完成后,您将能够将错误/异常转换为正确的HTTP响应。

中间件位置

此包提供用于处理错误的两个中间件:错误日志和错误转换。

它们设计用于在HTTP中间件管道的开始处使用,紧接在内容协商中间件之后。

<?php
use Lcobucci\ContentNegotiation\ContentTypeMiddleware;
use Lcobucci\ErrorHandling\ErrorConversionMiddleware;
use Lcobucci\ErrorHandling\ErrorLoggingMiddleware;

// In a Laminas Mezzio application, it would look like this:
$application->pipe(ContentTypeMiddleware::fromRecommendedSettings( /* ... */ )); // Very first middleware
$application->pipe(new ErrorConversionMiddleware( /* ... */ ));
$application->pipe(new ErrorLoggingMiddleware( /* ... */ ));

// all other middleware.

这样我们就可以按正确的顺序进行日志记录和转换,将内容协商和格式化委托给使用配置的格式化程序的ContentTypeMiddleware

重要

ErrorConversionMiddleware 使用 UnformattedResponseContentTypeMiddleware 执行格式化。请确保您已为MIME类型 application/problem+json 和/或 application/problem+xml 配置了格式化程序。

它还使错误/异常在响应的 error 属性中可用,因此您可以通过使用位于 ErrorConversionMiddlewareContentTypeMiddleware 之间的另一个中间件来访问它(如果需要的话)。

配置转换中间件的行为

您可以使用两个扩展点来做到这一点:调试信息策略和状态码提取策略。

您还可以通过在您的异常中实现某些接口来配置响应体属性。

调试信息策略

这定义了响应体中的 _debug 属性应该如何生成。我们提供了两个默认实现——一个是为生产模式设计的,另一个是为开发模式设计的。

要配置此策略,您必须将所需实现(或自定义实现)作为 ErrorConversionMiddleware 构造函数的第二个参数传递。

要提供自己的实现,您需要创建一个实现 DebugInfoStrategy 接口的新类。

状态码提取策略

这定义了如何将错误/异常转换为HTTP状态码。我们为此提供了一个基于类映射的单个默认实现。

要配置此策略,您必须将所需实现(或自定义实现)作为 ErrorConversionMiddleware 构造函数的第三个参数传递。

要提供自己的实现,您需要创建一个实现 StatusCodeExtractionStrategy 接口的新类。

默认类映射

默认映射使用该包中的标记接口来执行此类转换。如果错误/异常没有实现任何标记接口,则将使用错误/异常代码(当它不同于零时),或者回退到状态代码500(内部服务器错误)。

默认映射是

  • Lcobucci\ErrorHandling\Problem\InvalidRequest -> 400
  • Lcobucci\ErrorHandling\Problem\AuthorizationRequired -> 401
  • Lcobucci\ErrorHandling\Problem\Forbidden -> 403
  • Lcobucci\ErrorHandling\Problem\ResourceNotFound -> 404
  • Lcobucci\ErrorHandling\Problem\Conflict -> 409
  • Lcobucci\ErrorHandling\Problem\ResourceNoLongerAvailable -> 410
  • Lcobucci\ErrorHandling\Problem\UnprocessableRequest -> 422
  • Lcobucci\ErrorHandling\Problem\ServiceUnavailable-> 503

这允许我们创建自己的异常,它们将自动转换为正确的状态代码

<?php
declare(strict_types=1);

namespace My\Fancy\App\UserManagement;

use Lcobucci\ErrorHandling\Problem\ResourceNotFound;
use RuntimeException;

final class UserNotFound extends RuntimeException implements ResourceNotFound
{
}

重要:您不应该实现超过一个标记接口,否则您可能会得到意外结果。

自定义响应体属性

使用此库,您可以修改生成的响应的 typetitle 属性,并且还可以向其中添加新成员。

这是通过实现 TypedTitled 和/或 Detailed 接口来完成的——您不一定需要实现所有这些接口,只需要实现您想要的那些。

以下示例显示了如何表示 RFC 7807 中的其中一个示例

<?php
declare(strict_types=1);

namespace My\Fancy\App\UserManagement;

use Lcobucci\ErrorHandling\Problem\Forbidden;
use Lcobucci\ErrorHandling\Problem\Typed;
use Lcobucci\ErrorHandling\Problem\Titled;
use Lcobucci\ErrorHandling\Problem\Detailed;
use RuntimeException;
use function sprintf;

final class InsufficientCredit extends RuntimeException implements Forbidden, Typed, Titled, Detailed
{
    private $currentBalance;

    public static function forPurchase(int $currentBalance, int $cost): self
    {
        $exception = new self(sprintf('Your current balance is %d, but that costs %d.', $currentBalance, $cost));
        $exception->currentBalance = $currentBalance;

        return $exception;
    }

    public function getTypeUri(): string
    {
        return 'https://example.com/probs/out-of-credit';
    }

    public function getTitle(): string
    {
        return 'You do not have enough credit.';
    }

    /** @inheritDoc */
    public function getExtraDetails(): array
    {
        return ['balance' => $this->currentBalance]; // you might add "instance" and "accounts" too :)
    }
}

许可证

MIT,请参阅 LICENSE