romaninsh / validation
Agile Toolkit 验证器
Requires
- php: >=5.3
- atk4/atk4: >=4.2
This package is auto-updated.
Last update: 2024-09-14 09:36:10 UTC
README
这是一个强大的验证库,适用于您的 Agile Toolkit 应用程序和其他应用程序。
Agile Validation 的目标
很少有验证库具有简单和简洁的语法,因此我制作了这个插件。
Agile Validation 继承了框架的设计原则,其设计类似于 jQuery Chains 或 DQSL 链接。以下是一个典型的验证查询
$validator->is('name|len|<5?Name is too short');
该库允许我们创建多个规则,这些规则以一致的模式依次检查。以下是我在 Agile Validation 库中实现的一些目标
1. 极简语法
开发者通常避免验证,因为它需要他们编写大量的代码。验证应该容易定义且容易更新。当前的库需要使用复杂的结构,并需要大量编写。
Agile Toolkit 已经提供了基于异常的验证,您的模型可以在 beforeSave 或 afterLoad 钩子内部执行任何可想的验证。显示错误和检查字段的机制是存在的,但这种方法通常要求开发者编写大量代码而不是简短的规则集。
使用 Agile Validation 库,您可以以简短和智能的方式定义规则
$model->add('Validator')
->is([
'name,surname!|len|5..100',
'addr1_postcode|to_alpha|to_trim|uk_zip?Not a UK postcode',
'addr2_postcode|if|has_addr1|as|addr1_postcode'
])->on('beforeSave');
上面的代码可能看起来很神秘,但一旦你学会了阅读规则,你将喜欢在你的应用程序中使用它们。
让我快速带你了解上面的例子。
- 该示例包含 3 个规则集,每个规则集分别应用和检查。
- 规则应用于 $model 对象的字段
- 在模型保存之前检查规则
- 第一个规则集应用于 2 个字段 - 名称和姓氏
- "!" 将要求两个字段(???) 都不为空
- "len" 转换器将对字段的长度应用任何进一步的规则
- 5..100 指定名称和姓氏允许的长度范围
- 如果名称或姓氏缺失或其长度超出指定范围,将显示相应的错误消息
- 第二个规则集应用于 addr1_postcode 字段,并使用一些“规范器”
- 字段将过滤为字母字符并裁剪
- 规范化后,将检查其是否符合英国邮编格式
- "uk_zip" 规则使用自定义错误消息,该消息将出现在表单字段的下方
- 第三个规则应用于 addr2_postcode 字段(第二地址的邮编)
- 规则仅在 "has_addr1" 字段为 true 时应用
- 规则继承自 addr1_postcode 字段的规则 - 因此也将进行规范化
2. 极佳的独特概念
验证作为插件实现,因此如果您不喜欢这种方法,就不必使用它。验证引擎的概念基于几个关键概念,您在使用之前必须了解。
- 字段选择器 - 指定字段
- 规则处理 - 规则如何应用
- 规则类型 - 过滤器、转换器等
- 使用规则规范化字段值
- 别名和更简短的语法
- 逻辑运算
- 使用闭包/回调
- 添加自己的规则
- 扩展验证器以针对特殊字段(例如上传图像验证)
3. 集成
验证器可以独立工作,但与 Agile Toolkit 的其他部分紧密结合。您有权控制验证器操作何时执行。默认情况下,验证在保存模型之前发生,但这可以进行更改。
您还可以使用支持数组访问的简单数组(哈希)或任何其他对象与验证器一起使用,即使是从 Agile Toolkit 外部的对象。在这种情况下,为了指定数据存储的位置,您可以使用 with()
方法。
4. 错误信息
编写有意义的错误信息是一个大问题。这就是 Agile Validator 的目标之一,旨在自动生成合理的错误信息。(???错误信息的本地化???)
入门指南
要创建第一个验证,请在您的模型 init 方法中使用以下内容
$this->add('Validator')
->is('age|numeric|>10')
->is('email|email');
接下来,使用您的模型创建 CRUD 并尝试添加值
- 年龄作为字符串
- 年龄值10或以下
- 电子邮件格式不正确
在尝试保存这些不正确的值时,您应该在字段下方看到适当的错误信息。
理解规则格式
规则集是一组规则,由两部分组成
- 字段选择器
- 一个或多个规则
在最基本的形式中,规则集看起来像这样
$validator->is('field|rule|rule|rule');
或
$validator->is('field','rule','rule','rule');
在使用管道时,有时更容易阅读,尤其是如果您知道 UNIX 中管道的工作方式。您还可以指定 is()
方法的无限数量的参数,并且这种方法略灵活,因为在这种情况下,您还可以将管道用作值的一部分。
1. 规则处理
验证器存储您定义的所有规则,但在某些回调发生之前不会应用它们。如果您在 Model
中添加了验证器,则默认回调是 beforeSave
。如果您在 Form
中添加了验证器,则规则将在 submit
事件期间进行检查。
您还可以通过调用 now()
方法手动处理规则,或使用 on()
指定不同的钩子。
在处理规则之前,您可以添加任意多的规则。您还可以将规则分组在数组中,并将它们输入到验证器中
$validator->is(array(
'field|rule|rule',
'field|rule|rule'
))
或者如果您想避免使用管道
$validator->is(array(
array('field','rule','rule'),
array('filed','rule','rule')
));
2. 参数消耗
某些规则需要参数。例如,in
规则用于检查值是否存在于数组中。在 in
之后的所有参数都视为可能值的列表,而不是规则
$validator->is('state','in','paid,draft,new,old');
或
$validator->is('state','in',array('paid','draft','new','old'));
如果您想了解特定规则需要多少参数,必须检查该规则的文档。
3. 别名
为了使语法更短,使用了多种特殊的规则格式。例如
$validator->is('age','>18');
等同于
$validator->is('age','gt',18);
您必须记住,每个简写语法背后都有一个长语法。
字段定义
第一个参数始终定义字段或字段。验证器方法 expandField
负责将表示法转换为字段列表。
示例
- 包括字段
- 单个字段:"email"
- 多个字段:"email,name,surname"
- 通配符:"*_date" 或 "user_"
- 所有字段:"*"(通配符的特殊情况)
- 排除字段 - 以短横线 "-" 开头
- 除姓名和姓氏之外的所有字段:"*,-name,-surname"
- 匹配 "_date" 的所有字段,排除匹配 "_accept" 的字段:"_date,-accept"
- 排除具有优先权
验证器将在指定的时间(例如 beforeSave)处理此内容。使用星号或通配符假定您的数据源已从 Model 扩展,具有 getAllData()
方法(???getAllData() 是什么?可能是 getRows()???)或可以传递给 array_keys()
。
注意:您只能使用字母数字和下划线符号作为字段名!
指定字段数组
您可以使用数组指定字段列表。以下示例将创建一个规则集,该规则集将应用于两个字段并要求指定两个字段。
$validator->is(array('name','email'),'!');
以下示例是为了提醒您,is()
也可以将第一个参数作为 "multi-array"(???它真的是多数组吗???)
$validator->is(array('name!','email!'));
在这种情况下将创建两条规则,每条规则对应一个字段,并且要求指定该字段。接下来,除非重要,否则我将不再指出指定规则集的不同方法,所以请记住所有可能性。
模型字段组
模型支持字段组
$model->addField('has_addr')->type('boolean');
$model->addField('address')->group('addr');
$model->addField('zip')->group('addr');
现在您可以按组指定字段
$validator->is('[addr]|if|has_addr')
您也可以使用星号或通配符符号
$validator->is('[*addr]|if|has_addr')
感叹号
注意:验证规则仅用于验证。它们不会影响表单的展示。这就是为什么您仍然可以在字段定义内指定字段类型、显示选项和其他标志。
感叹号可以出现在字段或规则的末尾
$validator->is('name!');
$validator->is('username|to_alpha!');
如上所示使用感叹号将转换为以下规则
$validator->is('name|trim|required');
$validator->is('username|to_alpha|trim|required');
Trim将删除初始、尾随和重复空格。如果您不希望修剪值,则应使用全格式化的'required'规则
$validator->is('name','required'); // will allow you to use " " as name
字段比较
您可以在字段定义中使用等号来比较两个字段。以下是一个简短示例和生成的规则
$validator->is('pass2=pass1'); // same as:
$validator->is('pass2','eqf','pass1');
问号
如果您以问号结束字段,那么它被认为是必填字段,并带有用户定义的错误消息
$validator->is('name?type your name here'); // same as:
$validator->is('name|trim|required?type your name here');
??? 本地化 ???
与模型的字段一起使用
如果您
- 在模型中定义属性$validator_class。默认值为"Validator",但您可以使用自己的类。
- 在字段上调用->validate()方法。
以下代码
$model->addField('age')->validate('int|>20?You must be over 20');
与以下代码相同
$model->add($model->validator_class)->is('age|int|>20?You must be over 20');
注意:validate()方法返回字段对象以便进行链式调用,而不是Validator对象。
规则定义
随着您对验证器了解的深入,您必须理解其工作方式的一个重要概念
- 当处理新规则时,值从数据源复制到一个临时变量中,我将它称为
accumulator
。 - 规则可以访问
accumulator
和字段的名称。 - 规则可以“读取”下一个规则集的参数并将它们用作验证的参数。
- 规则可以访问当前数据源记录的其他字段。
过滤规则
如果规则查看accumulator
并根据条件抛出异常,那么它被称为Filter Rule
function rule_int($acc)
{
$v = $acc;
if (!is_int($v)) {
throw $this->exception('Must be int');
}
return $acc; // always return original value
}
一些过滤规则包括:int
、regexp_match
、email
、alpha
注意:过滤规则不会更改原始和accumulator
值。
转换规则
如果规则查看accumulator
并返回非空值,那么这个新值将存储在accumulator
中,供下一个规则集操作使用。这样的规则被称为Convertor Rule
function rule_len($acc)
{
return strlen($acc); // return changed value
}
一些转换规则包括:trim
、len
、date_diff
注意:转换规则不会更改原始值。如果需要,它只会更改accumulator
值。
规范化规则
开发者经常忽略规范化,它确保用户输入看起来整洁美观。例如,当用户输入电子邮件地址时,他们通常会留下空格,或者在指定数字时可能会不小心粘贴一些字符,如数字旁边的回车或制表符。
最好在保存之前清理这些值。许多规则可以使用“to_”前缀使用。这将导致验证器在规则处理完成后更新数据源以accumulator
的值。
例如(??? 都可以工作 ???)
email|to_trim|to_email
或
email|trim|to_email
如果您在验证规则中添加一个尾随竖线(??? 我不喜欢尾随竖线的想法。在这种情况下应该使用另一个符号。竖线用于分隔元素,所以就这样吧 ???),那么这将把accumulator
复制回数据源
email|trim|email|
我强烈建议您在软件中使用规范化。但是您必须小心使用,因为规范化有时会导致不希望的结果
email|trim|len|>5|
由于尾部管道,这将会用长度替换电子邮件。可能不是你想要的。正确的规则应该是
email|to_trim|len|>5
注意:规范化规则可能会改变原始和 accumulator
的值。
多字段操作,复制
有时你会在多个字段之间执行操作,例如在另一个字段中存储一个字段的长度或将一个字段拆分为两个字段。这可以通过仔细应用转换器并使用 copy
规则来完成。此规则将复制指定字段的 accumulator
值。
->is('name|copy|full_name|trim|s/.* //|')
->is('surname|copy|full_name|trim|s/ .*//|')
->is('name_length|copy|name|len|')
注意:基本上,copy
将原始字段值更改为从子规则传递的无限长参数的 accumulator
值。
别名
当使用管道分隔的规则格式时,只能安全地使用字母数字字符、数字和下划线作为规则和值。其他字符通常保留为别名。对于规则来说,这是可以接受的,因为规则本质上是在使用PHP方法名。
当规则包含任何其他字符时,它被视为别名,验证器将尝试将其转换为常规规则。以下列出的别名是按验证顺序排列的
a-z
-> alphaa-z0-9
-> alpha_num (??? 区分大小写 ???)0-9a-z
-> alpha_num (??? 区分大小写 ???)!
-> mandatory2..4
-> between|2|4>4
-> gt|4!=5
-> ne|5 (??? 这可能与required
或mandatory
规则难以区分 ???)b-z
-> regexp_match|/^[b-z]*$//^a/
-> regexp_match|/^a/s/a/z/
-> to_regexp|a|z (??? z 是什么,为什么需要尾部斜杠 ???)
错误消息
每个规则都定义了一个适当的错误消息。例如,规则 ">20" 生成消息 "{{caption}} 必须大于 {{arg1}}"。)
如果你使用了某些转换器,它们也可能更改错误消息:"{{caption}} 的长度"
"Length of Name must be more than 20"
你可以通过将自定义错误消息附加到规则后跟一个问号来指定它
>20?Must be over 20
所有错误消息都通过异常传递,这也意味着错误消息将通过 $this->api->_($error)
进行本地化。有关更多信息,请参阅本地化文档。(??? 在 ATK 中没有完全工作的内置本地化支持 ???))
如果你愿意指定一些包含危险字符的复杂错误消息,可以使用以下格式
$validtor->is('age','int?','Must be numeric');
如果规则也期望一个参数,那么该规则参数必须首先给出。
$validator->is('age','!=?','10','Age must be 10');
(??? 我不喜欢 !=? 因为 ! 和 = 是两个不同的规则,应该用管道符号分开 ???)
在错误消息中,你还可以使用一些参数
- {{name}} - 字段的实际名称(例如 user_name)
- {{caption}} - 字段的标题(如果模型),表单的标签或与 {{name}} 相同
- {{arg1}} - 规则的第一个参数
- {{arg2}} - 规则的第二个参数
- {{arg3}} 以及更多。
闭包的使用
我之前解释了 rule_X 方法是如何被调用以及它们如何接收 accumulator
。如果你将闭包指定为规则,那么这个闭包将被调用。第一个参数是验证器对象,第二个参数是 accumulator
,第三个是字段的名称。你可以与验证器接口进行交互以执行更复杂的操作。请参阅下面的 "Validator's Methods"。(??? 没有这样的章节 "Validator's Methods" ???))
示例
->validate('birthdate',function($v,$acc){
$d = new DateTime($acc);
return $d->diff(new DateTime())->format('%y')
},'>=18?Must be at least {{arg1}} years old');
比较
比较规则的命名灵感来自 UNIX bash 比较操作。 >5, <5, >=5, <=5, =5, !=5
被转换为 gt, lt, ge, le, eq, ne
。所有方法都将消耗下一个参数并将其用作比较值。如果参数是数组,则该数组的元素被视为子规则。
子规则
坦白说——到目前为止所做的一切,子规则是直观的下一步。子规则将暂停你的规则处理,以通过另一个规则,然后用 resulting accumulator
替换它。
$validator->is(
'password1',
'len',
'eq?Password length must be the same',array('password2','len')
);
您还可以通过使用as
规则显式调用子规则。通常,as
的参数是收集规则的字段名,但它也可以从数组参数中读取规则。
例如:(此处需要示例)
宏/使用不存在的字段
您可以使用规则系统创建不存在的字段,然后引用它们
$validator->is(array(
'#myemail|s/.*<//|s/>.*//|to_trim|email',
'client_email|as|#myemail',
'billing_email|as|#myemail'
));
让我们用敏捷工具包通常的方式将这个问题提升到另一个层面
class MyValidator extends Validator
{
function init()
{
parent::init();
$this->is(array(
'#myemail|s/.*<//|s/>.*//|to_trim|email',
'#zip|s/.*<//|s/>.*//|to_trim|email',
));
}
接下来,您可以为此模型指定此验证器,并依赖于现在可以用作宏的不存在的字段
$validator->is('email|as|#myemail');
如果您为as
指定错误消息,它将使用它而不是子规则内部生成的错误消息
$validator->is('email|as?Does not match fancy email format|#myemail');
并且/或者
您可能依赖于And/Or逻辑来定义多个字段之间的复杂依赖关系
$validator->is(':or', rules1, rules2, rules3)
$validator->is(':and', rules1, rules2, rules3)
示例
$validator->is(
':or?Must be male over 10y or female over 12y',
array(':and','gender|=M','age>10'),
array(':and','gender|=F','age>12')
)
(???
- 相当奇怪的语法。
- 不确定要显示哪个错误消息 - "or"规则设置的,还是子规则生成的。(此处需要示例)
单位转换
有一些转换规则可以将您的单位转换为kb
、mb
或gb
。这些转换器会将累加器
除以1024适当次数。
条件规则 - if(数组)
默认情况下,if
规则消耗下一个参数并将其用作判断其他字段是否指定的依据。如果您想使用更复杂的检查,如果也支持子规则
$validator->is('addr','if',array('method','=','deliver'))
您还可以将回调函数用作参数
$validator->is('addr', 'if?Must specify address if you deliver',
function($v, $addr, $addr_name, $data) {
return $data['method'] == 'deliver';
})
如果指定了3个参数,则规则'if'将消耗最多3个参数。您可以通过提供null或空字符串来跳过参数。第一个参数可以是回调函数或子规则。如果未指定第二个参数,则字段将简单地被验证为必填项,如果第一个参数中的回调函数或子规则返回“true”。如果指定了第二个参数,它将用作规则,仅在if为true时应用。第三个参数是“else”-规则。
$validator->is('delivery_to','if','home','[home_addr]!','[work_addr]!')
您可以通过将if
作为最后一个规则来省略参数。在您的规则集中有if
将不会绕过其之前的任何规则。
比较字段
当您使用比较操作符(通过其别名‘=’或使用规则名‘eq’)时,您指定值
$validator->is('gender','=M')
$validator->is('gender','=','M')
$validator->is('gender','eq','M')
如果您想与其他字段比较,您可以在字段内部指定它或使用以“f”结尾的方法之一:eqf, nef, ltf, gtf, lef, get
$validator->is('pass1=pass2')
$validator->is('pass1','eqf','pass2');
数组成员
使用“in”和“!in”(或not_in)可以验证元素是否在允许的值集中
$validator->is('gender|in|M,F')
$validator->is('gender','in',array('M','F'))
第二种格式允许您使用数组中的任何值,它们甚至可以包含逗号或管道。
扩展 - Validator_Table
敏捷工具包通过多个类实现验证器
- AbstractValidator - 仅实现规则逻辑,不实现实际字段。条件、比较和子规则逻辑已实现。还将支持
Model
数据源。 - Validator - 添加通常用于验证的规则。
- Validator_Table - 假设您使用
Model_Table
并引入了基于ORM的扩展。
Validator
规则已在上文解释,但Validator_Table
提供了以下增强功能
避免重复(唯一)
在某些情况下,您可能希望避免有多个具有相同电子邮件地址的用户账户。规则'unique'将尝试查找具有相同电子邮件地址的另一个记录,如果找到,则生成错误。
$validator->is('email|unique');
在DSQL中查找
使用$dsql,您可以指定一个子查询,该子查询可以用于查找有效元素
$validator->is('email','in',$dsql);
验证器将执行如下查询:“select 1 if [field] in ($dsql)”(使用正确的引号)。
验证其他模型
您可以创建一个规则,该规则尝试从另一个模型中加载数据记录
$validator->is('user_id','loadable','User');
如果通过hasOne
定义字段,您也可以避免模型
$validator->is('user_id','loadable');
与模型集成,此规则可以非常简单
$this->hasOne('User')->validate('loadable');
扩展 - Validator_Image
当您使用Filestore插件时,它还会引入一个额外的验证器类,可以用于对图像字段进行进一步的检查。以下是语法
$model->add('filestore/Field_Image')
->validate('format|=jpeg')
->validate('size|mb|<5');
您可以多次调用validate()方法或指定包含规则的数组。
- format - 返回上传图像的格式
- size - 返回上传文件的字节大小
- height - 返回像素高度
- width - 返回像素宽度
默认情况下,在图像字段上定义的验证将使用上传钩子,因此错误将不会在表单提交时显示,而是在您上传图像时显示。
更多示例
$validator->is(array(
'email|to_email|!', # convert to email and must not be empty
'base_price|to_int|10..100', # convert to int, and must be within range
'postcode|to_upper|to_trim|to_A-Z|postcode',
# clean up postcode then validate
'pass1=pass2', # passwords should match
'country_code|upper|in|UK,US,DE,FR',
# uppercase for comparison only
'addr2|asif|addr1', # validate addr2 like addr1 if addr1 is present
'hobby|s/[^,]//|len|>5?Max of 5 hobbies can be specified'
));
更改钩子
如我所述,默认情况下,验证是在模型的beforeSave钩子中执行的。如果与表单一起使用,则验证将在提交期间执行。可以通过使用@hook来更改特定规则的钩子。这会被转换为"on"规则
$validator->is('name|to_lower|@afterLoad')
$validator->is('name|to_lower|on|afterLoad')
$validator->is('name','to_lower','on',array($api,'post-init'))
这将仅影响单个规则,并可能导致创建另一个Controller_Validator副本,因此请使用验证器的->on方法,而不是为每个单独的规则使用此方法。
其他想法
验证器可以像上面经常解释的那样标准化规则定义。尽管这不会支持所有情况,但如果您愿意进行基于客户端的验证(在浏览器或移动应用中),标准化可以非常强大。
$validator->getRules('field');
这将返回类似这样的规则集数组
array(
array('rule','rule','rule',$arg,'rule'),
array('rule','rule','rule')
);
每个规则名称都应使用字母数字和下划线表示。参数可以是值或值的数组。