m1x0n / opis-json-schema-error-presenter
opis库的JSON schema错误展示器
v0.5.3
2023-03-14 11:08 UTC
Requires
- php: >=7.1
- ext-json: *
- opis/json-schema: ^1.0
Requires (Dev)
- dg/bypass-finals: ^1.1
- opis/json-schema: ^1.0
- phpstan/phpstan: ^1
- phpunit/phpunit: >=7.1 <10
- squizlabs/php_codesniffer: ^3.4
README
警告
⚠️此库可能由于新主要版本(2.0.0)的opis/json-schema已支持错误格式化而变得过时。
为opis/json-schema库生成的JSON schema验证错误提供可定制的错误展示器:JSON schema实现。
换句话说,它是对Opis\JsonSchema\ValidationError
集合以人类可读方式表示的直接尝试。
要求
- php >= 7.1
- opis/json-schema
安装
composer require m1x0n/opis-json-schema-error-presenter
使用示例
use Opis\JsonSchema\Schema; use Opis\JsonSchema\ValidationResult; use Opis\JsonSchema\Validator; use OpisErrorPresenter\Contracts\PresentedValidationError; use OpisErrorPresenter\Implementation\MessageFormatterFactory; use OpisErrorPresenter\Implementation\PresentedValidationErrorFactory; use OpisErrorPresenter\Implementation\ValidationErrorPresenter; require __DIR__ . '/../vendor/autoload.php'; $jsonSchema ='{ "$schema": "https://json-schema.fullstack.org.cn/draft-07/schema#", "$id": "http://example.com/product.schema.json", "title": "Product", "description": "A product from Acme\'s catalog", "type": "object", "properties": { "productId": { "type": "integer", "minimum": 1 }, "productName": { "type": "string", "minLength": 3 }, "price": { "type": "object", "properties": { "amount": { "type": "integer", "minimum": 0, "maximum": 1000 }, "currency": { "type": "string", "enum": ["USD", "EUR", "BTC"] } }, "required": ["amount", "currency"] } }, "required": [ "productId", "productName", "price" ] }'; $data = '{ "productId": "123", "productName": "XX", "price": { "amount": 200, "currency": "GBP" } }'; $data = json_decode($data); $jsonSchema = Schema::fromJsonString($jsonSchema); $validator = new Validator(); // Get all errors. Yeah -1 here. /** @var ValidationResult $result */ $result = $validator->schemaValidation($data, $jsonSchema, -1); // Default strategy is AllErrors $presenter = new ValidationErrorPresenter( new PresentedValidationErrorFactory( new MessageFormatterFactory() ) ); $presented = $presenter->present(...$result->getErrors()); // Inspected presenter error print_r(array_map(static function (PresentedValidationError $error) { return $error->toArray(); }, $presented)); // Json-serializable echo json_encode($presented);
输出结果示例
Array
(
[0] => Array
(
[keyword] => type
[pointer] => productId
[message] => The attribute expected to be of type 'integer' but 'string' given.
)
[1] => Array
(
[keyword] => minLength
[pointer] => productName
[message] => The attribute length should be at least 3 characters.
)
[2] => Array
(
[keyword] => enum
[pointer] => price/currency
[message] => The attribute must be one of the following values: 'USD', 'EUR', 'BTC'.
)
)
展示策略
AllErrors
- 显示所有可用的展示错误FirstError
- 选择展示错误中的第一个BestMatchError
- 评估最佳匹配错误
为了指定策略,只需将选定的策略传递给PresentedValidationErrorFactory
,例如
$presenter = new ValidationErrorPresenter( new PresentedValidationErrorFactory( new MessageFormatterFactory() ), new BestMatchError() );
自定义翻译
有自定义翻译的可能性。目前只有DefaultTranslator
公开了一些通用消息,例如
属性长度应至少为3个字符
为了替换、扩展或提出新的翻译,必须实现MessageTranslator
接口。例如
<?php declare(strict_types=1); namespace Acme; use OpisErrorPresenter\Contracts\Keyword; use OpisErrorPresenter\Implementation\MessageFormatterFactory; use OpisErrorPresenter\Implementation\PresentedValidationErrorFactory; use OpisErrorPresenter\Implementation\Translators\DefaultTranslator; use OpisErrorPresenter\Implementation\ValidationErrorPresenter; class InternationalTranslator extends DefaultTranslator { protected $messages = []; private const DEFAULT_MESSAGE = 'The attribute is invalid'; public function __construct() { parent::__construct(); $this->loadMessages(); } public function translate(string $key, array $replacements = [], $locale = null): string { if ($locale && array_key_exists($locale, $this->messages)) { $message = $this->messages[$locale][$key] ?? self::DEFAULT_MESSAGE; return strtr($message, $replacements); } // Fallback on default locale return parent::translate($key, $replacements, $locale); } private function loadMessages(): void { /* Locales structure example: [ 'locale_1' => [ 'keyword' => 'translation_1' ... ], 'locale_2' => [ 'keyword' => 'translation_2' ... ], ... ] */ $this->messages = [ 'de_DE' => [ // The rest of other keywords ... Keyword::MIN_LENGTH => 'Die Attributlänge sollte mindestens betragen: min: Zeichen.' // ... ], 'ru_RU' => [ // ... Keyword::ENUM => 'Длина атрибута должна быть минимум :min: символов.' // .... ] ]; } } // Then configure presenter factory $presenter = new ValidationErrorPresenter( new PresentedValidationErrorFactory( new MessageFormatterFactory(), new InternationalTranslator() ) );
区域设置解析
为了获得更好的不同本地化体验,可以使用自定义翻译器与自动区域设置解析一起使用。
目前实现了以下区域设置解析器
NullLocaleResolver
作为通用消息的回退。HttpLocaleResolver
尝试根据HTTP_ACCEPT_LANGUAGE
头检测区域设置,并需要安装ext-intl
。
也可以通过实现LocaleResolver
接口来实现自定义区域设置解析器。
为了设置HttpLocaleResolver
或自定义的解析器
$presenter = new ValidationErrorPresenter( new PresentedValidationErrorFactory( new MessageFormatterFactory(), new InternationalTranslator(), new HttpLocaleResolver() ) );
区域设置支持
目前有使用通过ArrayLocaleLoader
定义的区域设置的可能性。见lang/en.php
。
为了加载更多语言,可以按照以下方式配置ValidationErrorPresenter
$presenter = new ValidationErrorPresenter( new PresentedValidationErrorFactory( new MessageFormatterFactory(), new DefaultTranslator( (new ArrayLocaleLoader())->addPath('de', '/path_to_lang/lang/de.php') ) ) );
但是,可以通过实现LocaleLoader
接口从任何地方加载区域设置字符串。
此外,可以使用单个区域设置配置展示器。为此,可以使用FixedLocaleResolver
。例如
$presenter = new ValidationErrorPresenter( new PresentedValidationErrorFactory( new MessageFormatterFactory(), new DefaultTranslator( (new ArrayLocaleLoader())->addPath('cs', '../lang') ), new FixedLocaleResolver('cs') ) );
可以通过使用DI-container稍微简化所有配置。