验证与错误处理¶
验证和错误处理是映射过程的重要方面:必须信任将非结构化输入转换为严格类型的数据结构。如果发现无效值,则无法保证所需结构的有效性,因此映射将失败。
当映射失败时,会抛出\CuyZ\Valinor\Mapper\MappingError类型的异常,其中包含有关映射过程中遇到的所有错误的信息。因此,始终建议使用 try/catch 语句包装映射函数调用并正确处理错误。
try {
(new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(SomeClass::class, [/* … */ ]);
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
// Get a flattened list of all messages detected during mapping
$messages = $error->messages();
// Formatters can be added and will be applied on all messages
$messages = $messages->formatWith(
new \CuyZ\Valinor\Mapper\Tree\Message\Formatter\MessageMapFormatter([
// …
]),
(new \CuyZ\Valinor\Mapper\Tree\Message\Formatter\TranslationMessageFormatter())
->withTranslations([
// …
])
);
foreach ($messages as $message) {
echo $message;
}
}
自定义异常消息¶
更具体的验证应该在对象的构造函数中完成,如果给定的数据有问题,则抛出异常。
出于安全原因,映射器不会捕获构造函数中引发的异常,除非使用以下三个选项之一。
1. 自定义异常类¶
可以抛出实现\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage的异常。正文可以包含占位符,请参阅消息自定义章节以获取更多信息。
如果可以提供更多参数,异常还可以实现接口\CuyZ\Valinor\Mapper\Tree\Message\HasParameters该接口返回字符串值列表,使用键作为参数名称。
为了帮助识别错误,可以通过实现接口CuyZ\Valinor\Mapper\Tree\Message\HasCode来提供唯一代码。
final readonly class SomeClass
{
public function __construct(private string $value)
{
if ($this->value === 'foo') {
throw new SomeException('some custom parameter');
}
}
}
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
final class SomeException extends DomainException implements ErrorMessage, HasParameters, HasCode
{
private string $someParameter;
public function __construct(string $someParameter)
{
parent::__construct();
$this->someParameter = $someParameter;
}
public function body() : string
{
return 'Some custom message / {some_parameter} / {source_value}';
}
public function parameters(): array
{
return [
'some_parameter' => $this->someParameter,
];
}
public function code() : string
{
// A unique code that can help to identify the error
return 'some_unique_code';
}
}
try {
(new \CuyZ\Valinor\MapperBuilder())->mapper()->map(SomeClass::class, 'foo');
} catch (\CuyZ\Valinor\Mapper\MappingError $exception) {
// Should print:
// Some custom message / some custom parameter / 'foo'
echo $exception->messages()->toArray()[0];
}
2. 使用提供的消息生成器¶
实用程序类\CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder可用于构建消息。
final readonly class SomeClass
{
public function __construct(private string $value)
{
if (str_starts_with($this->value, 'foo_')) {
throw \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder::newError(
'Some custom error message: {value}.'
)
->withCode('some_code')
->withParameter('value', $this->value)
->build();
}
}
}
try {
(new \CuyZ\Valinor\MapperBuilder())->mapper()->map(
SomeClass::class, 'foo_bar'
);
} catch (\CuyZ\Valinor\Mapper\MappingError $exception) {
// Should print:
// > Some custom error message: foo_bar.
echo $exception->messages()->toArray()[0];
}
3. 允许第三方异常¶
可以设置映射器可以捕获的异常列表,例如在使用 [Webmozart Assert] 等轻量级验证工具时。
建议谨慎使用此功能:用户态异常可能包含敏感信息 - 例如,绝不允许显示查询的一部分的 SQL 异常。因此,只应过滤精心挑选的例外情况的详尽列表。
final readonly class SomeClass
{
public function __construct(private string $value)
{
\Webmozart\Assert\Assert::startsWith($value, 'foo_');
}
}
try {
(new \CuyZ\Valinor\MapperBuilder())
->filterExceptions(function (Throwable $exception) {
if ($exception instanceof \Webmozart\Assert\InvalidArgumentException) {
return \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder::from($exception);
}
// If the exception should not be caught by this library, it
// must be thrown again.
throw $exception;
})
->mapper()
->map(SomeClass::class, 'bar_baz');
} catch (\CuyZ\Valinor\Mapper\MappingError $exception) {
// Should print something similar to:
// > Expected a value to start with "foo_". Got: "bar_baz"
echo $exception->messages()->toArray()[0];
}