雪软/schema

📐 Nette Schema: 对给定的 Schema 进行数据结构验证。

v0.01 2023-05-01 13:13 UTC

This package is auto-updated.

Last update: 2024-08-30 01:49:53 UTC


README

Nette Schema

Downloads this Month Tests Coverage Status Latest Stable Version License

介绍

一个用于对给定的 Schema 进行数据结构验证和规范化的实用库,具有智能且易于理解的 API。

文档可以在网站上找到。

安装

composer require nette/schema

它需要 PHP 版本 8.0,并支持 PHP 8.0 到 8.2。

支持我

你喜欢 Nette Schema 吗?你在期待新功能吗?

Buy me a coffee

谢谢!

基本用法

在变量 $schema 中我们有一个验证 Schema(具体是什么意思以及如何创建它我们稍后会说),在变量 $data 中我们有一个想要验证和规范化的数据结构。这可以是,例如,通过 API 发送的由用户发送的数据,配置文件等。

这个任务由 Nette\Schema\Processor 类处理,它处理输入并返回规范化数据,或者在出错时抛出 Nette\Schema\ValidationException 异常。

$processor = new Nette\Schema\Processor;

try {
	$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
	echo 'Data is invalid: ' . $e->getMessage();
}

方法 $e->getMessages() 返回所有消息字符串的数组,而 $e->getMessageObjects() 返回所有消息作为 Nette\Schema\Message 对象。

定义 Schema

现在我们来创建一个 Schema。使用类 Nette\Schema\Expect 来定义它,我们实际上定义了数据的期望外观。假设输入数据必须是一个包含元素 processRefund 的结构(例如数组)类型为 bool 和 refundAmount 类型为 int。

use Nette\Schema\Expect;

$schema = Expect::structure([
    'processRefund' => Expect::bool(),
    'refundAmount' => Expect::int(),
]);

我们认为 Schema 定义看起来很清晰,即使是你第一次看到它。

让我们发送以下数据进行验证

$data = [
    'processRefund' => true,
    'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, it passes

输出,即值 $normalized,是 stdClass 对象。如果我们想让输出是一个数组,我们向 Schema 添加一个类型转换 Expect::structure([...])->castTo('array')

结构中的所有元素都是可选的,并具有默认值 null。示例

$data = [
    'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, it passes
// $normalized = {'processRefund' => null, 'refundAmount' => 17}

默认值是 null 并不意味着它会被接受在输入数据 'processRefund' => null 中。不,输入必须是布尔值,即只有 truefalse。我们必须通过 Expect::bool()->nullable() 明确允许 null

可以使用 Expect::bool()->required() 将项目变为必填。我们使用 Expect::bool()->default(false) 或简短地使用 Expect::bool(false) 将默认值更改为 false

如果我们还想接受布尔值以外的 10,我们可以列出允许的值,这些值也将被规范化为布尔值

$schema = Expect::structure([
    'processRefund' => Expect::anyOf(true, false, 1, 0)->castTo('bool'),
    'refundAmount' => Expect::int(),
]);

$normalized = $processor->process($schema, $data);
is_bool($normalized->processRefund); // true

现在你已经了解了如何定义 Schema 以及结构中各个元素的行为。我们现在将展示所有其他元素在定义 Schema 时的用途。

数据类型:type()

所有标准的 PHP 数据类型都可以在 Schema 中列出

Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])

然后所有通过Expect::type('scalar')或简写形式Expect::scalar()支持的 Validators 类型。也接受类或接口名称,例如Expect::type('AddressEntity')

您还可以使用联合表示法

Expect::type('bool|string|array')

默认值始终为null,除非是arraylist,其中为空数组。(列表是按升序索引的数组,即非关联数组)。

值数组:arrayOf() listOf()

数组结构过于通用,更实用的是指定它可以包含的确切元素。例如,只能包含字符串的数组

$schema = Expect::arrayOf('string');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string

列表是索引数组

$schema = Expect::listOf('string');

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // ERROR: 123 is not a string
$processor->process($schema, ['key' => 'a']); // ERROR: is not a list
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list

参数也可以是一个模式,因此我们可以这样写

Expect::arrayOf(Expect::bool())

默认值为空数组。如果您指定默认值并调用mergeDefaults(),则它将与传递的数据合并。

枚举:anyOf()

anyOf()是一组值或模式,值可以是这些之一。以下是如何编写一个可以包含'a'truenull的元素数组的示例

$schema = Expect::listOf(
	Expect::anyOf('a', true, null)
);

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ERROR: false does not belong there

枚举元素也可以是模式

$schema = Expect::listOf(
	Expect::anyOf(Expect::string(), true, null)
);

$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // ERROR

默认值为null

结构

结构是具有定义键的对象。每个键 => 值对都称为“属性”

结构接受数组和对象,并返回stdClass对象(除非您使用castTo('array')等更改它)。

默认情况下,所有属性都是可选的,默认值为null。您可以使用required()定义必需属性

$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // the default value is null
]);

$processor->process($schema, ['optional' => '']);
// ERROR: item 'required' is missing

$processor->process($schema, ['required' => 'foo']);
// OK, returns {'required' => 'foo', 'optional' => null}

尽管null是可选属性的默认值,但在输入数据中不允许使用(值必须是字符串)。接受null的属性使用nullable()定义

$schema = Expect::structure([
	'optional' => Expect::string(),
	'nullable' => Expect::string()->nullable(),
]);

$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' expects to be string, null given.

$processor->process($schema, ['nullable' => null]);
// OK, returns {'optional' => null, 'nullable' => null}

默认情况下,输入数据中不允许有额外的项

$schema = Expect::structure([
	'key' => Expect::string(),
]);

$processor->process($schema, ['additional' => 1]);
// ERROR: Unexpected item 'additional'

我们可以使用otherItems()更改这一点。作为参数,我们将指定每个额外元素的架构

$schema = Expect::structure([
	'key' => Expect::string(),
])->otherItems(Expect::int());

$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // ERROR

弃用

您可以使用deprecated([string $message])方法弃用属性。自v1.1以来,弃用警告由$processor->getWarnings()返回

$schema = Expect::structure([
	'old' => Expect::int()->deprecated('The item %path% is deprecated'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["The item 'old' is deprecated"]

范围:min() max()

使用min()max()限制数组的元素数量

// array, at least 10 items, maximum 20 items
Expect::array()->min(10)->max(20);

对于字符串,限制它们的长度

// string, at least 10 characters long, maximum 20 characters
Expect::string()->min(10)->max(20);

对于数字,限制它们的值

// integer, between 10 and 20 inclusive
Expect::int()->min(10)->max(20);

当然,您可以只提及min()或只提及max()

// string, maximum 20 characters
Expect::string()->max(20);

正则表达式:pattern()

使用pattern(),您可以指定一个正则表达式,整个输入字符串必须匹配(即,就像它被字符^$包裹一样)

// just 9 digits
Expect::string()->pattern('\d{9}');

自定义断言:assert()

您可以使用assert(callable $fn)添加任何其他限制。

$countIsEven = function ($v) { return count($v) % 2 === 0; };

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // the count must be even

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 is not even

或者

Expect::string()->assert('is_file'); // the file must exist

您可以为每个断言添加自己的描述。它将是错误消息的一部分。

$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Even items in array');

$processor->process($schema, ['a', 'b', 'c']);
// Failed assertion "Even items in array" for item with value array.

该方法可以重复调用以添加更多断言。

映射到对象:from()

您可以从类生成结构架构。示例

class Config
{
	/** @var string */
	public $name;
	/** @var string|null */
	public $password;
	/** @var bool */
	public $admin = false;
}

$schema = Expect::from(new Config);

$data = [
	'name' => 'jeff',
];

$normalized = $processor->process($schema, $data);
// $normalized instanceof Config
// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false}

如果您正在使用PHP 7.4或更高版本,您可以使用原生类型

class Config
{
	public string $name;
	public ?string $password;
	public bool $admin = false;
}

$schema = Expect::from(new Config);

匿名类也受支持

$schema = Expect::from(new class {
	public string $name;
	public ?string $password;
	public bool $admin = false;
});

由于从类定义中获得的信息可能不足,您可以为元素添加自定义架构作为第二个参数

$schema = Expect::from(new Config, [
	'name' => Expect::string()->pattern('\w:.*'),
]);

转换:castTo()

成功验证的数据可以转换

Expect::scalar()->castTo('string');

除了原生PHP类型之外,您还可以将数据转换到类

Expect::scalar()->castTo('AddressEntity');

规范化:before()

在验证之前,可以使用before()方法对数据进行规范化。以下是一个示例,元素必须是一个字符串数组(例如['a', 'b', 'c']),但接收到的输入是字符串a b c

$explode = function ($v) { return explode(' ', $v); };

$schema = Expect::arrayOf('string')
	->before($explode);

$normalized = $processor->process($schema, 'a b c');
// OK, returns ['a', 'b', 'c']