snicco/psr7-error-handler

psr7应用的一个强大且可定制的错误处理器。

v2.0.0-beta.9 2024-09-07 14:27 UTC

README

codecov Psalm Type-Coverage Psalm level PhpMetrics - Static Analysis PHP-Versions

Snicco项目(Snicco)的ErrorHandler组件是一个独立的错误处理器,用于使用PSR-7请求的PHP应用程序。

目录

  1. 安装
  2. 需要的协作者
    1. RequestAwareLogger
    2. ExceptionInformationProvider
    3. ExceptionDisplayer
  3. 完整示例
  4. 异常工具
  5. 贡献
  6. 问题和PR
  7. 安全

安装

composer require snicco/psr7-error-handler

HTTP错误处理器的协作者

在高层,HttpErrorHandler接口负责将Throwable实例转换为Psr\Http\Message\ResponseInterface实例。

此包提供了一个TestErrorHandler,它只是重新抛出异常,以及一个ProductionErrorHandler,这是本文档的主要内容。

为了实例化ProductionErrorHandler我们需要以下协作者:

RequestAwareLogger

RequestAwareLogger是一个围绕PSR-3 logger的简单包装类。

它允许您根据捕获的异常、当前请求等,为每个日志条目添加日志上下文

RequestAwareLogger::__construct()传入的最后一个参数是可变参数,接受RequestLogContext实例。

以下是使用方法

(Monolog只是一个示例,您可以使用任何PSR-3 logger。)

use Psr\Log\LogLevel;
use Snicco\Component\Psr7ErrorHandler\Information\ExceptionInformation;
use Snicco\Component\Psr7ErrorHandler\Log\RequestAwareLogger;
use Snicco\Component\Psr7ErrorHandler\Log\RequestLogContext;

$monolog = new Monolog\Logger();

$request_aware_logger = new RequestAwareLogger($monolog);

// With custom exception levels.
// By default, any status code > 500 will be LogLevel::CRITICAL
// Anything below will be LogLevel::ERROR
$request_aware_logger = new RequestAwareLogger($monolog, [
    Throwable::class => LogLevel::ALERT,
    MyCustomException::class => LogLevel::WARNING  
])

// With custom log context:
class AddIPAddressFor403Exception implements RequestLogContext {

    public function add(array $context, ExceptionInformation $information) : array{
        
        if(403 === $information->statusCode()) {
            $context['ip'] = $information->serverRequest()->getServerParams()['REMOTE_ADDR'];
        }
        return $context;
    }
}

// The last argument is variadic.
$request_aware_logger = new RequestAwareLogger($monolog, [], new AddIPAddressFor403Exception());

ExceptionInformationProvider

ExceptionInformationProvider负责将Throwable实例转换为ExceptionInformation实例。

ExceptionInformation是一个值对象,由以下内容组成

  • 一个用于异常的唯一标识符,将作为日志上下文传递并显示给用户。
  • 在显示异常时应使用的HTTP状态码。
  • 一个安全的异常标题,用于显示(安全意味着“不包含敏感信息”)。
  • 一个安全的消息,用于显示异常(安全意味着“不包含敏感信息”)。
  • 原始的Throwable
  • 转换后的Throwable
  • 原始的Psr\Http\Message\ServerRequestInterface实例

此包附带了一个InformationProviderWithTransformation实现。

您可以如下实例化此类

use Snicco\Component\Psr7ErrorHandler\Identifier\SplHashIdentifier;
use Snicco\Component\Psr7ErrorHandler\Information\InformationProviderWithTransformation;

// uses spl_object_hash to uniquely identify exceptions.
$identifier = new SplHashIdentifier();

// This will use the error messages in /resources/en_US.error.json
$information_provider = InformationProviderWithTransformation::fromDefaultData($identifier);

// Or with custom error messages
$error_messages = [
    // The 500 status code is mandatory. All other HTTP status codes are optional.
    500 => [
        'title' => 'Whoops, this did not work...',
        'message' => 'An error has occurred... We are sorry.'
    ];
]
$information_provider = new InformationProviderWithTransformation($error_messages, $identifier);

正如其类名所暗示的,InformationProviderWithTransformation 允许您将异常转换为其他类型的异常。

这是通过使用 ExceptionTransformers 来实现的。

以下是如何将自定义异常类转换为 HttpException 实例的示例。

use Snicco\Component\Psr7ErrorHandler\Information\ExceptionTransformer;

class CustomAuthenticationTo404Transformer implements ExceptionTransformer {
    
    public function transform(Throwable $e) : Throwable{
        
        if(!$e instanceof MyCustomAuthenticationException) {
            return $e;
        }
        
        // Key value pairs of headers that will later be added to the PSR-7 response.
        $response_headers = [
            'WWW-Authenticate' => '/login'    
        ];
        
        // The status code that should be used for the PSR-7 response.
        $status_code = 401;
        
        return \Snicco\Component\Psr7ErrorHandler\HttpException::fromPrevious($e, $status_code, $response_headers);
    }
}

$identifier = new SplHashIdentifier();

$information_provider = InformationProviderWithTransformation::fromDefaultData(
    $identifier,
    new CustomAuthenticationTo404Transformer() // Last argument is variadic
);

如果您没有提供任何 ExceptionTransformers,则每个异常都将转换为状态码为 500HttpException(除非它已经是 HttpException 的实例)。

ExceptionDisplayer

ExceptionDisplayer 负责显示 ExceptionInformation

ExceptionDisplayer 只支持一种内容类型。

ProductionExceptionHandler 接受一个或多个 ExceptionDisplayers,并将确定当前请求的最佳显示程序。

此软件包附带两个默认显示程序,将用作后备。

通过使用 DisplayerFilters 来确定异常/请求组合的最佳显示程序。

此软件包默认附带以下过滤器

  • Delegating,委派到其他过滤器。
  • CanDisplay,根据 ExceptionDisplayer::canDisplay() 的返回值进行过滤。
  • Verbosity,根据 ExceptionDisplayer::isVerbose() 的返回值以及当前请求的详细程度进行过滤。
  • ContentType,根据 ExceptionDisplayer::supportedContentType() 的返回值以及当前请求的 Accept 标头进行过滤。! 重要:此过滤器仅执行非常基本的内容协商。内容协商超出了此软件包的范围,应在中间件中执行。

完整工作示例

以下是如何实例化 ProductionErrorHandler 的示例,最好是在您的依赖注入容器中。

use Psr\Log\LogLevel;
use Snicco\Component\Psr7ErrorHandler\DisplayerFilter\CanDisplay;
use Snicco\Component\Psr7ErrorHandler\DisplayerFilter\ContentType;
use Snicco\Component\Psr7ErrorHandler\DisplayerFilter\Delegating;
use Snicco\Component\Psr7ErrorHandler\DisplayerFilter\Verbosity;
use Snicco\Component\Psr7ErrorHandler\Identifier\SplHashIdentifier;
use Snicco\Component\Psr7ErrorHandler\Information\InformationProviderWithTransformation;
use Snicco\Component\Psr7ErrorHandler\Log\RequestAwareLogger;
use Snicco\Component\Psr7ErrorHandler\ProductionErrorHandler;

// Use any PSR-7 response factory
$psr_7_response_factory = new Nyholm\Psr7\Factory\Psr17Factory();

$request_aware_logger = new RequestAwareLogger(
    new Monolog\Logger(), // Use any PSR-3 logger
);

$information_provider = InformationProviderWithTransformation::fromDefaultData(
    new SplHashIdentifier()
);

$prefer_verbose = (bool) getenv('APP_DEBUG');

$displayer_filter = new Delegating(
    new ContentType(),
    new Verbosity($prefer_verbose),
    new CanDisplay(),
);

$error_handler = new ProductionErrorHandler(
    $psr_7_response_factory,
    $request_aware_logger,
    $information_provider,
    $displayer_filter,
    // Custom exception displayers go here (variadic)
)

然后使用实例化的错误处理器,如下所示:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\MiddlewareInterface;
use Snicco\Component\Psr7ErrorHandler\HttpErrorHandler;

class ErrorHandlerMiddleware implements MiddlewareInterface {
    
    private HttpErrorHandler $error_handler;
    
    public function __construct(HttpErrorHandler $error_handler) {
        $this->error_handler = $error_handler;
    }
    
    public function process(ServerRequestInterface $request,RequestHandlerInterface $handler) : ResponseInterface{
        
        try {
            return $handler->handle($request);
        }catch (Throwable $e) {
            return $this->error_handler->handle($e, $request);
        }
    }
    
}

异常工具

面向用户的异常

此包附带一个UserFacing接口,您的自定义异常可以实现该接口。

如果抛出一个实现UserFacing的异常,将使用UserFacing::safeTitle()UserFacing::safeMessage()的返回值来创建ExceptionInformation,而不是默认的HTTP错误消息,这些消息可能对用户来说没有意义。

原始异常消息将被记录,而您的用户将看到他们可以相关联的东西(稍微多一点)。

HTTP异常

此包附带一个通用的HTTPException类,您可以在您的HTTP相关代码中抛出(主要是中间件)。

这允许您指定HTTP响应代码,以及可选的附加响应头。

使用HTTPException类和使用ExceptionTransformer之间的区别在于,后者旨在用于您的领域异常,而HTTPExceptions应该只在HTTP相关代码(如中间件和控制器)中抛出。

贡献

此存储库是Snicco项目开发存储库的只读拆分。

以下是您如何贡献的方法.

报告问题和发送拉取请求

请将问题报告在Snicco单体存储库中。

安全

如果您发现安全漏洞,请遵循我们的披露程序