nette / schema
📐 Nette Schema:根据给定的模式验证数据结构。
Requires
- php: 8.1 - 8.3
- nette/utils: ^4.0
Requires (Dev)
- nette/tester: ^2.4
- phpstan/phpstan-nette: ^1.0
- tracy/tracy: ^2.8
This package is auto-updated.
Last update: 2024-09-07 00:52:52 UTC
README
Nette Schema
简介
一个用于根据给定模式验证和规范数据结构的实用库,具有智能且易于理解的API。
文档可以在网站上找到。
安装
composer require nette/schema
需要PHP版本8.1,并支持PHP 8.1至8.4。
支持我
你喜欢Nette 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 对象。
定义模式
现在让我们创建一个模式。我们使用类 Nette\Schema\Expect 来定义它,我们实际上定义了对数据应该是什么样的期望。假设输入数据必须是一个包含元素 processRefund
(布尔类型)和 refundAmount
(整型)的结构(例如数组)。
use Nette\Schema\Expect; $schema = Expect::structure([ 'processRefund' => Expect::bool(), 'refundAmount' => Expect::int(), ]);
我们认为模式定义看起来很清晰,即使你第一次看到它。
让我们发送以下数据进行验证
$data = [ 'processRefund' => true, 'refundAmount' => 17, ]; $normalized = $processor->process($schema, $data); // OK, it passes
输出,即 $normalized
的值,是 stdClass
对象。如果我们想输出是一个数组,我们向模式添加一个类型转换 Expect::structure([...])->castTo('array')
。
结构中的所有元素都是可选的,默认值为 null
。示例
$data = [ 'refundAmount' => 17, ]; $normalized = $processor->process($schema, $data); // OK, it passes // $normalized = {'processRefund' => null, 'refundAmount' => 17}
默认值是 null
并不意味着它会接受在输入数据中 'processRefund' => null
。不,输入必须是布尔值,即只有 true
或 false
。我们必须通过 Expect::bool()->nullable()
显式允许 null
。
可以使用 Expect::bool()->required()
将项目变为必需的。我们使用 Expect::bool()->default(false)
或简短地使用 Expect::bool(false)
将默认值更改为 false
。
如果我们想接受布尔值之外的 1
和 0
,我们列出允许的值,这些值也会被规范化为布尔值
$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
现在你了解了模式定义的基础以及结构中各个元素的行为。我们现在将展示在定义模式时可以使用所有其他元素。
数据类型:type()
在模式中可以列出所有标准的PHP数据类型
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
,除了对于array
和list
,它们的默认值是一个空数组。(列表是一个按数字键升序索引的数组,即非关联数组)。
值数组: 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
第二个参数可以用来指定键(自版本 1.2 开始)
$schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int
列表是一个索引数组
$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'
、true
或null
元素的数组
$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
anyOf()
方法接受变体作为单独的参数,而不是作为数组。要传递一个值数组,请使用展开运算符anyOf(...$variants)
。
默认值是null
。使用firstIsDefault()
方法将第一个元素设置为默认值
// default is 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
结构体
结构体是具有定义键的对象。这些键 => 值对中的每一个都被称为“属性”
结构体接受数组和对象,并返回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: option 'required' is missing $processor->process($schema, ['required' => 'foo']); // OK, returns {'required' => 'foo', 'optional' => null}
如果您不想输出仅具有默认值的属性,请使用skipDefaults()
$schema = Expect::structure([ 'required' => Expect::string()->required(), 'optional' => Expect::string(), ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); // OK, returns {'required' => 'foo'}
尽管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])
方法弃用属性。弃用通知由$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 = fn($v) => 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.
该方法可以重复调用以添加多个约束。它可以与对transform()
和castTo()
的调用混合使用。
转换:transform()
可以使用自定义函数修改成功验证的数据
// conversion to uppercase: Expect::string()->transform(fn(string $s) => strtoupper($s));
该方法可以重复调用以添加多个转换。它可以与对assert()
和castTo()
的调用混合使用。操作将按它们声明的顺序执行
Expect::type('string|int') ->castTo('string') ->assert('ctype_lower', 'All characters must be lowercased') ->transform(fn(string $s) => strtoupper($s)); // conversion to uppercase
transform()
方法可以同时进行转换和验证值。这通常比链式调用 transform()
和 assert()
更简单、更少冗余。为此,该函数接收一个带有 addError()
方法的 Nette\Schema\Context 对象,可以用来添加关于验证问题的信息
Expect::string() ->transform(function (string $s, Nette\Schema\Context $context) { if (!ctype_lower($s)) { $context->addError('All characters must be lowercased', 'my.case.error'); return null; } return strtoupper($s); });
类型转换:castTo()
成功验证的数据可以被转换
Expect::scalar()->castTo('string');
除了原生 PHP 类型外,还可以转换为类。它区分是否有构造函数的简单类或带构造函数的类。如果类没有构造函数,会创建一个其实例,并将结构体中的所有元素写入其属性
class Info { public bool $processRefund; public int $refundAmount; } Expect::structure([ 'processRefund' => Expect::bool(), 'refundAmount' => Expect::int(), ])->castTo(Info::class); // creates '$obj = new Info' and writes to $obj->processRefund and $obj->refundAmount
如果类有构造函数,则将结构体的元素作为命名参数传递给构造函数
class Info { public function __construct( public bool $processRefund, public int $refundAmount, ) { } } // creates $obj = new Info(processRefund: ..., refundAmount: ...)
与标量参数结合的类型转换会创建一个对象,并将值作为唯一参数传递给构造函数
Expect::string()->castTo(DateTime::class); // creates new DateTime(...)
规范化:before()
在验证本身之前,可以使用 before()
方法对数据进行规范化。例如,有一个必须为数组字符串(例如 ['a', 'b', 'c']
)的元素,但接收到的输入形式为字符串 a b c
$explode = fn($v) => explode(' ', $v); $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); // OK, returns ['a', 'b', 'c']
对象映射:from()
您可以从类生成结构模式。例如
class Config { public string $name; public ?string $password; public bool $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}
也支持匿名类
$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:.*'), ]);