网络模式
一个实用的库,用于通过智能且易于理解的 API 根据给定模式验证和规范化数据结构。
文档可以在网站上找到。
安装:
composer require nette/schema
它需要 PHP 版本 8.1,并支持 PHP 最高版本 8.4。
你喜欢 Nette Schema 吗?您期待新功能吗?
谢谢你!
在变量$schema
中,我们有一个验证模式(这到底意味着什么以及如何创建它,我们稍后会说),在变量$data
中,我们有一个要验证和规范化的数据结构。例如,这可以是用户通过 API、配置文件等发送的数据。
该任务由 NetteSchemaProcessor 类处理,该类处理输入并返回规范化数据或在出错时引发 NetteSchemaValidationException 异常。
$ 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()
将所有消息作为 NetteSchemaMessage 对象返回。
现在让我们创建一个模式。类 NetteSchemaExpect 用于定义它,我们实际上定义了数据应该是什么样子的期望。假设输入数据必须是一个结构体(例如数组),其中包含 bool 类型的processRefund
元素和 int 类型的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
现在您了解了架构的定义方式以及结构的各个元素的行为方式的基础知识。现在,我们将展示可用于定义模式的所有其他元素。
所有标准 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()
支持的所有类型。还接受类或接口名称,例如Expect::type('AddressEntity')
。
您还可以使用联合表示法:
Expect:: type ( ' bool|string|array ' )
默认值始终为null
除了array
和list
之外,它是一个空数组。 (列表是按数字键从零开始升序索引的数组,即非关联数组)。
数组是一种过于通用的结构,准确指定它可以包含哪些元素会更有用。例如,一个数组,其元素只能是字符串:
$ 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
参数也可以是一个schema,所以我们可以这样写:
Expect:: arrayOf (Expect:: bool ())
默认值为空数组。如果您指定默认值并调用mergeDefaults()
,它将与传递的数据合并。
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
是optional
属性的默认值,但在输入数据中不允许使用 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()
限制数组元素的数量:
// 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()
,您可以指定整个输入字符串必须匹配的正则表达式(即,就好像它被包裹在字符^
a $
中一样):
// just 9 digits
Expect:: string ()-> pattern ( ' d{9} ' );
您可以使用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()
调用混合。
可以使用自定义函数修改成功验证的数据:
// 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()
方法的 NetteSchemaContext 对象,该对象可用于添加有关验证问题的信息:
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 );
});
成功验证的数据可以被转换:
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()
方法对数据进行标准化。举个例子,我们有一个元素,它必须是字符串数组(例如['a', 'b', 'c']
),但接收字符串abc
形式的输入:
$ explode = fn ( $ v ) => explode ( ' ' , $ v );
$ schema = Expect:: arrayOf ( ' string ' )
-> before ( $ explode );
$ normalized = $ processor -> process ( $ schema , ' a b c ' );
// OK, returns ['a', 'b', 'c']
您可以从类生成结构模式。例子:
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:.* ' ),
]);