dakujem / strata
分层异常处理机制的合约。
Requires
- php: >=8.0
Requires (Dev)
- nette/tester: ^2.4.1
This package is auto-updated.
Last update: 2024-09-17 09:40:44 UTC
README
分层异常处理机制的合约和实现。
💿
composer require dakujem/strata
📒 变更日志
TL;DR
原生PHP异常允许携带字符串消息和整型代码。
👉 这还不够。 👈
拥抱具有上下文信息的异常。
具有上下文信息的异常
- 向客户端传达信息
- 为开发者携带元数据
HTTP客户端错误示例
use Dakujem\Strata\Http\UnprocessableContent; if(!isEmail($input['email'])){ // HTTP 422 throw (new UnprocessableContent('Invalid e-mail address.')) // Convey messages to clients with localization support and metadata: ->convey( message: __('Please enter a valid e-mail address.'), source: 'input.email', ) // Add details for developers to be reported or logged: ->pin([ 'name' => $input['name'], 'email' => $input['email'], ], key: 'input'); }
内部逻辑故障
use Dakujem\Strata\LogicException; throw (new LogicException('Invalid value of Something.')) // Convey messages and meta-data to clients, humans and apps alike: ->convey( message: __('We are sorry, we encountered an issue with Something and are unable to fulfil your request.'), source: 'something.or.other', description: __('We are already fixing the issue, please try again later.'), meta: ['param' => 42] ) // Add details for developers to be reported or logged: ->explain('Fellow developers, this issue is caused by an invalid value: ' . $someValue) ->pin(['value' => $someValue, 'severity' => 'serious'], 'additional context') ->pin(['other' => $value], 'other context') ->pin('anything') ->tag('something') ->tag(tag: 'serious', key: 'severity') ;
元数据可以被服务器端错误处理器用来报告详细数据。
面向客户端的数据可以被客户端应用程序使用,并显示给最终用户或进行程序处理。
这对于API+客户端应用程序架构(JS小部件、PWA、移动应用程序等)特别有用。
使用错误处理器处理上下文
通常,您的应用程序将有一个全局错误处理机制,无论是启动时的try-catch块、错误处理中间件还是原生错误处理器。
类似于以下内容
try { process_request(Request::fromGlobals()); } catch (Throwable $e) { handle_exception($e); }
例如,在Laravel中,通常是由 App\Exceptions\Handler
类来处理异常。
在Slim中, 中间件用于错误处理。
Symfony 也会捕获所有异常和错误,并且可以自定义处理逻辑。
所有这些都需要开发者想出特定的异常,这些异常携带特定的上下文,如Guzzle的 RequestException
携带HTTP请求和响应。
这很正常。无论如何,请为特定目的创建特定的异常。
Strata通过实现单个接口(SupportsContextStrata
)和使用单个特性(ContextStrata
)来帮助启用上下文支持。
也有时候,一个人不需要特定的异常,但仍然希望将上下文数据传递给全局错误处理器。
Strata为常见的HTTP响应提供异常。
为了报告或记录上下文,或者为了向前端消费者提供信息,strata提供接口和实现。
示例
Laravel中异常处理器的示例(JSON API)
namespace App\Exceptions; use Dakujem\Strata\Contracts\IndicatesAuthenticationFault; use Dakujem\Strata\Contracts\IndicatesAuthorizationFault; use Dakujem\Strata\Contracts\IndicatesClientFault; use Dakujem\Strata\Contracts\IndicatesConflict; use Dakujem\Strata\Contracts\IndicatesInvalidInput; use Dakujem\Strata\Support\ErrorContainer; use Dakujem\Strata\Support\SuggestsHttpStatus; use Dakujem\Strata\Support\SupportsPublicContext; class Handler extends ExceptionHandler { protected function prepareJsonResponse($request, Throwable $e) { $errors = $e instanceof SupportsPublicContext ? $e->publicContext() : null; $errors ??= []; if ($errors === []) { $message = $detail = null; // Public error message for clients: if ($e instanceof SuggestsErrorMessage) { $message = $e->suggestErrorMessage(); } // Laravel/Symfony HTTP exception if ($e instanceof HttpExceptionInterface) { $message = $e->getMessage(); } // Slim HTTP exception if ($e instanceof HttpException) { $message = $e->getTitle(); $detail = $e->getDescription(); } $errors[] = new ErrorContainer( message: $message, detail: $detail, ); } // Status code $code = 500; if ($e instanceof SuggestsHttpStatus) { $code = $e->suggestStatusCode(); } elseif ($e instanceof IndicatesInvalidInput) { $code = 422; // 422 Unprocessable Content } elseif ($e instanceof IndicatesConflict) { $code = 409; // 409 Conflict } elseif ($e instanceof IndicatesAuthorizationFault) { $code = 403; // 403 Forbidden } elseif ($e instanceof IndicatesAuthenticationFault) { $code = 401; // 401 Unauthorized } elseif ($e instanceof IndicatesClientFault) { $code = 400; // 400 Bad Request } elseif ($e instanceof HttpExceptionInterface) { // Laravel/Symfony HTTP exceptions $code = $e->getStatusCode(); } elseif ($e instanceof ValidationException) { $code = $e->status; // 422 Unprocessable Content (default) } elseif ($e instanceof HttpException) { // Slim HTTP exception $code = $e->getCode(); } return response() ->json( data: [ 'errors' => $errors, ], ) ->withStatus( $code, ); } }
处理内部上下文以改进 Sentry 报告的示例
use Dakujem\Strata\Support\SupportsInternalContext; use Dakujem\Strata\Support\SupportsInternalExplanation; use Dakujem\Strata\Support\SupportsTagging; use Sentry\Event; use Sentry\EventHint; use Sentry\State\HubInterface; use Sentry\State\Scope; function reportException(Throwable $e) { $hub = Container::get(HubInterface::class); $hub->configureScope(function (Scope $scope) use ($e): void { // Internal context comprises all the pinned data (see `pin` method usage above) if ($e instanceof SupportsInternalContext) { foreach ($e->context() as $key => $value) { if (is_string($value) || is_numeric($value) || $value instanceof Stringable) { $value = [ 'value' => (string)$value, ]; } if (is_object($value)) { $value = (array)$value; } if (is_array($value)) { $scope->setContext( is_numeric($key) ? 'context-' . $key : $key, $value, ); } } } if ($e instanceof SupportsTagging) { foreach ($e->tags() as $key => $value) { // When tags have numeric keys, use tag:true format, otherwise use key:tag format. $scope->setTag( is_numeric($key) ? $value : $key, is_numeric($key) ? 'true' : $value, ); } } if ($e instanceof SupportsInternalExplanation) { $scope->setContext( '_dev_', [ 'explanation' => $e->explanation(), ], ); } }); $event = Event::createEvent(); $event->setMessage($e->getMessage()); $hint = new EventHint(); $hint->exception = $e; $hub->captureEvent($event, $hint); }
API设计的著名合约
自动错误处理的合约,特别是对于HTTP API非常有用
IndicatesClientFault
4xxIndicatesServerFault
5xx
if($exception instanceof IndicatesClientFault){ return convert_client_exception_to_4xx_response($exception); } if($exception instanceof IndicatesServerFault){ report_server_fault($exception); return apologize_for_server_issue_with_5xx_status($exception); }
常见的HTTP 4xx异常
此软件包提供常见4xx HTTP状态响应的异常。
- 400
BadRequest
- 404
NotFound
- 403
Forbidden
- 401
Unauthorized
- 409
Conflict
- 422
UnprocessableContent
有关更多信息,请参阅 HTTP状态参考。
自己构建
strata合约和特性允许并鼓励开发者轻松地为特定用例创建自己的异常。
class MySpecificException extends WhateverBaseException implements SupportsContextStrata { use ContextStrata; public function __construct($message = null, $code = 0, Throwable $previous = null) { parent::__construct( $message ?? 'This is the default message for my specific exception.', $code ?? 0, $previous, ); } }
如果只需要选择机制,请使用下表选择特定接口和特性
为了提供具有HTTP能力的可抛出对象,实现以下简单接口
SuggestsErrorMessage
向错误处理器建议错误信息SuggestsHttpStatus
向错误处理器建议HTTP状态码
兼容性和支持
此包需要 PHP >=8.0
。没有其他外部依赖。
存在PHP 7.4的向后移植版本:
dakujem/strata74