dakujem / strata74
dakujem/strata (分层异常处理机制合约) 的 PHP 7.4 版本回端口
Requires
- php: 7.4.*
- ext-json: *
Replaces
- dakujem/strata: ^1.0
README
✋ 请注意
这是原始包
dakujem/strata
的回端口,仅适用于 PHP 7.4。基于 PHP 8 版本的
v1.0
版本。在更新到 PHP 8 后,只需将要求更改为
dakujem/strata
。
很可能,您不需要做任何事情。
如果您的类直接实现了 strata 接口,您只需要更新实现的方法(pin
、replaceContext
、pass
、explain
、tag
和convey
)的类型提示。PHP 8 版本改进了键处理(支持整数键,与 PHP 数组键处理对齐)和完整的测试覆盖率。
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
4xx错误IndicatesServerFault
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状态参考。
构建自己的
分层合约和特性允许并鼓励开发者为特定用例轻松创建自己的异常。
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状态码