bluem / validation
简单独立的验证库
Requires
- php: >=5.3.0
README
概要
Validation 是一个简单独立的验证和(在一定程度上)归一化库,适用于 PHP 5.3 或更高版本。它可以用来验证单个值、单个对象的属性或对象的所有属性 - 要么在数组中定义验证规则,要么使用类(域对象)中属性注释中的属性进行验证。
为什么还需要另一个验证库?
这是一个非常好的问题,很高兴你问了。确实,有很多 PHP 验证库,其中一些非常好。
例如,有令人愉悦的 Symfony Validator 组件(我在需要的时候也会使用它)。当使用 Symfony2 或所谓的“胖”Silex 发行版时,它很容易集成,但如果不是这样,你必须自己设置东西,这至少意味着你还需要使用翻译组件(除非你对此英语本地化感到满意)。即使这样,它也无法处理一些本地化值,所以你必须自己添加约束。例如,当你想确保一个日期小于另一个日期时,情况也是如此。
这就是这个 Validator 库更方便的地方:它没有任何依赖,可以处理本地化输入,例如数字(如果你曾经将德语“1.234,56”这样的货币值复制到不知道如何处理本地化十进制和千位分隔符的应用程序的输入字段中,你就知道我在说什么了)或日期,并能检查依赖关系(即:value1 小于|大于 value2 等)。显然的缺点是这个库可选的约束条件很少(我试图说:它没有那些你几乎永远不需要的约束条件),并且它的本地化支持目前相当有限(英语和德语)——尽管它可以很容易地扩展。
所以,总结一下:根据你的需求和项目,它可能是一个有用的解决方案,但可能不是“始终适用”的验证库。
安装
安装此库的首选方式是通过 Composer。为此,将 "bluem/validation": "~2.0"
添加到你的 composer.json
文件中的需求中。由于 Validation 使用 语义版本控制,当运行 composer update
时,你将获得修复和功能添加,但不会得到破坏 API 的更改。
或者,你可以使用 git 克隆仓库。
使用注解进行验证
步骤 1:通过编写注解定义约束
#!php
/**
* @var string
* @validation-type email
* @validation-label E-mail address
* @validation-mandatory IF NOT $phone
*/
protected $email = '';
/**
* @var string
* @validation-type string
* @validation-label Phone
* @validation-maxlength 15
* @validation-pattern /^[0-9 ()+-]$/
* @validation-mandatory IF NOT $email
*/
protected $phone = '';
/**
* @var string
* @validation-type url
* @validation-label Website URL
*/
protected $url = '';
步骤 2:实现你的设置器
#!php
/**
* @param string $value
* @throws InvalidArgumentException
*/
public function setEmail($value)
{
// The call to validatePropertyValue() will throw an InvalidArgumentException
// if it does not fulfill the rules/constraints.
$this->email = $this->validatePropertyValue($this, 'email', $value);
}
可选步骤 3:为整个类添加验证。
#!php
/**
* @param string $value
* @return array
*/
public function validate()
{
return $this->validator->validateObject($this);
}
就是这样。设置器确保属性不能设置为无效值,而类级别的设置确保当前值遵守这些约束,并且还检查条件约束,如上面的 @validation-mandatory
。在这个例子中,将 @validation-mandatory
设置为 IF NOT $email
和 IF NOT $phone
确保电子邮件地址或电话号码必须设置。
使用注解进行验证不仅可以让你通过调用validateObject()
轻松验证对象,如果你喜欢使用基于在类中实现__set()
方法的属性重载方法来设置setter,那么这种方法尤其方便。换句话说:你将属性名和值传递给__set()
,然后在__set()
内部,你只需要确保属性名存在(并且是Validator要验证的),并调用`tvalidatePropertyValueteProperty($this, $name, $value)`,这样你就有了对所有属性的验证。
基于注解的验证的一个不错特性是,“最小”和“最大”约束可以接受其他属性的名称作为值。这意味着如果你有两个实例变量$startmonth
和$endmonth
,你可以在$endmonth
上添加约束@validation-minimum $startmonth
(反之亦然),以确保日期在逻辑上是正确的。
使用显式约束进行验证
如果你不喜欢基于注解的方法或者需要无法仅通过注解表达的限制(例如,“这个日期的最小值是下个月第二个星期二”),你可以显式地调用validate()
。这在你不处理模型类,但需要对用户提供的简单数据进行直接验证的情况下也很有用。
实例化验证器的工作方式与通常一样,但不是调用validatePropertyValue()
,而是用约束作为参数1,用值作为参数2调用validate()
。
#!php
$validator = new \BlueM\Validation\Validator(
new BlueM\Validation\I18n\En()
);
try {
$value = $validator->validate(
array(
'type' => 'string',
'minlength' => 10,
'mandatory' => true,
),
$value
);
// $value is now the transformed/cleaned value (in case of a
// string: trimmed, vertical whitespace removed)
} catch (\InvalidArgumentException $e) {
// Validation failed
}
验证集合数据类型
如果你有实例变量,它们应该是一个数组,包含几个具有相同类型和约束的项,你可以使用集合。例如,在电子邮件地址集合的情况下,使用注解的代码如下所示:
#!php
/**
* @var array
* @validation-type email[]
*/
protected $emailAddresses = array();
现在,当让Validator验证值array('user1@example.com', 'user2@example.com')
时,验证将成功,而验证"Hello world"
或array(123, 'Not e-mail')
将失败。
ArrayCollections
(关联数组的索引数组)的工作方式类似,但在此情况下,你必须指定数据类型为array[]
,并可以为关联数组中的字段添加约束。
例如,让我们创建一个实例变量,它包含一个事件数组,每个事件都包含一个键为"startdate"(日期,必填)、"enddate"(日期,可选)和"title"(由10 ... 50个字符组成的字符串,必填)的数组。
#!php
/**
* @var array
* @validation-type array[]
* @validation-type:startdate date
* @validation-mandatory:startdate
* @validation-type:enddate date
* @validation-type:title string
* @validation-mandatory:title
* @validation-minlength:title 10
* @validation-maxlength:title 50
*/
protected $events = array();
Validator应该将此值视为有效
#!php
array(
array(
'startdate' => '2013-04-07',
'enddate' => '2013-04-11',
'title' => 'Title of event 1',
),
array(
'startdate' => '2014-02-25',
'title' => 'Title of event 2',
),
)
另一方面,尝试验证此值应该失败,因为第一个事件的开场日期无效,第二个事件的标题太短。
#!php
array(
array(
'startdate' => '2013-02-31',
'enddate' => '2013-04-11',
'title' => 'Title of event 1',
),
array(
'startdate' => '2014-02-25',
'title' => 'Short',
),
)
捕获ArrayCollection验证异常
当你验证ArrayCollections(关联数组的索引数组)时,你不能简单地使用setter来捕获类型为\InvalidArgumentException
的异常,因为如果你这样做,你就不会知道哪些值确切地导致了验证失败。
更准确的方法是捕获一个InvalidCollectionArgumentException
(关于完全限定名称,请参阅下面的代码),它有一个getErrors()
方法,它将返回精确信息,因此你可以显示错误字符串在元素旁边。
#!php
try {
$object->seEvents($events, $validator);
} catch (\BlueM\Validation\InvalidCollectionArgumentException $e) {
$errors = $e->getErrors();
// $errors is now an array with the numeric row index as key and
// assocative array(s) as value. For instance, in the last example
// above, $e->getErrors() would return this:
// array(
// 0 => array('startdate' => 'The date is invalid.')
// 1 => array('title' => 'The value must not have less than 10 characters.')
// )
} catch (\InvalidArgumentException $e) {
$error = $e->getMessage();
// $error is now a newline-separated string containing all errors
}
为“空白”或“必填”条件约束定义字段标签
当存在类型为Blank
或Mandatory
的条件约束时,它们可能需要在错误消息中引用其他属性(例如:“此值是必填的,如果字段foobar不为空。”)。
为此,你可以将标签数组作为validate()
和validateObject()
的第三个参数传递,或者作为validatePropertyValue()
、validateProperty()
的第四个参数传递。数组应是一个关联数组,其键与条件约束定义中使用的字符串相同,字段标签作为值。
示例:当您的属性注解包含类似于 @validation-mandatory IF $firstname
的内容时,数组应类似于 array('$firstname' => 'First name')
。
数据类型
目前支持以下数据类型
ArrayCollection
:一种关联数组的集合(索引数组),后者的数组具有某些约束Bool
:一个布尔值Collection
:一种包含一个或多个相同类型/具有相同约束的项目/类型的类型Date
:一个标量类型,用于存储日期表示(内部以“Y-m-d”格式表示),可以从本地化字符串设置Email
:最多254个字符的字符串,大致看起来像一个电子邮件地址,即:进行的测试不是RFC兼容的,而是检查最常见的错误Float
:一个浮点数,可以从本地化数字设置HourMinute
:小时加分钟(不是时间),内部以“H:m”格式的字符串表示Int
:一个整数,可以从本地化数字设置Month
:指定月份的字符串,例如“03/2012”或“2010-8”String
:单行字符串,最多不超过250个字符,并且自动删除前后空白Text
:单行或多行字符串,无长度限制,自动删除前后空白。换行符规范化为Unix风格(“\n”)Url
:代表HTTP或HTTPS URL的单行字符串。默认情况下,此字符串可能不超过150个字符,但可以通过使用maxlength
约束来更改。Xml
:XML字符串(只有当它是良好格式化的时才进行检查,目前没有进一步的验证)
除ArrayCollection和Collection外,您可以直接使用数据类型的低格式名称来告诉验证器期望哪种数据类型。但是,对于ArrayCollection和Collection来说,情况略有不同(请参阅上述示例):您必须使用array[]
(ArrayCollection)和string[]
(Collection)分别,在Collection的情况下,当然也可以是email[]
、int[]
等等。
约束
还有更多比以下列出的约束,但被排除的约束仅适用于内部使用,不适用于在注解中使用或指定验证规则。
decimalplaces
:确保给定的值(预期为浮点数)的十进制位数(小数点后的数字)不超过给定数量mandatory
:确保给定的值不是null、不是空字符串和空数组。maximum
:确保给定的值最大(这意味着什么取决于数据类型)不超过构造函数中给出的参数。maxlength
:确保给定的字符串(可能是数字的字符串表示)包含的字符数不超过给定数量mincount
:集合中项目的最小数量(这可能在未来版本中更改)minimum
:确保给定的值不小于(这意味着什么取决于数据类型)构造函数中给出的参数。minlength
:确保给定的字符串(可能是数字的字符串表示)包含的最小字符数notlocalized
:告诉验证器此值不得解释为本地化值pattern
:确保值与正则表达式匹配(应包括定界符和修饰符)valuelist
:值必须位于指定的值列表中。此列表预期为空白分隔的字符串(通常用于注解)或索引数组。wellformed
:确保给定的字符串格式正确。当然,这仅适用于预期为XML的字符串。
条件约束
条件约束是强制
约束,根据另一个实例变量是否为空来应用或忽略。
典型使用场景包括
包含名称和可选地址的表单;虽然没有地址是可以接受的,但你不会只想有仅邮编或仅城市——相反,如果这两个中的任何一个不为空,你需要两者(以及街道)。为此,将注解
@validation-mandatory IF city
添加到邮编,将@validation-mandatory IF zip
添加到城市。用户可以选择留下他的电子邮件地址或电话号码或两者都留下——但不能都不留下。为此,将注解
@validation-mandatory IF NOT email
添加到电话属性,将@validation-mandatory IF NOT phone
添加到电子邮件属性。
注意:在使用条件约束时,您可能需要注意设置属性的顺序。如果您有一个属性$b
,该属性在属性$a
不为空时是必需的,请确保在验证$b
之前先设置$a
的值。
使用自定义数据类型扩展验证
如果您想验证无法由内置数据类型处理的数据,您可以定义自定义数据类型来执行验证任务。
为此,以下是所需的步骤
- 创建验证器(如通常一样)
- 调用验证器的
addNamespace()
方法,并向它传递一个命名空间和(可选)一个额外的构造函数参数数组 - 在您通过
addNamespace()
传递的命名空间中实现所需的类型(必须从BlueM\Validation\Type
继承) - 使用类型名称,就像使用任何内置类型一样
示例
首先是设置
#!php
$validator = new \BlueM\Validation\Validator(
new \BlueM\Validation\I18n\En()
);
$validator->addNamespace('Mynamespace\Validation', array($this));
现在,类型
#!php
namespace Mynamespace\Validation\Type;
use BlueM\Validation\Constraint\Scalar;
use BlueM\Validation\I18n;
use BlueM\Validation\Transformer\Trim;
use BlueM\Validation\Type;
class FooType extends Type
{
public function __construct(I18n $i18n, $localized)
{
$this->addConstraint(new Scalar());
$this->addTransformer(new Trim());
...
...
}
}
请注意,类名需要“Type”后缀(如上面的代码所示),才能与库的版本3一起使用,而版本1/2可能不需要该后缀。
现在,您可以将“foo”用作数据类型,无论是作为注解还是作为描述验证约束的数组中的数据类型
您负责使类型类可加载。这意味着您要么提供自动加载机制,要么显式包含类。
添加约束
除了声明自定义类型之外,您还可以添加自定义约束。(实际上,自定义类型通常仅在使用自定义约束时才有意义。)
约束类必须继承自BlueM\Validation\Constraint
,并在其check()
方法检查失败时抛出BlueM\Validation\ValidationFailedException
。您可以将任意异常代码传递给ValidationFailedException
的构造函数,但建议使用BlueM\Validation\Validator
中的某个常量(如果没有合适的,还有一个通用的FAIL_GENERIC
常量),或者定义一个自己的类常量,然后覆盖自定义类型中的getExceptionMessage()
方法,该方法可以使用此常量来确定哪种验证失败。
作者 & 许可证
此代码由Carsten Blüm(www.bluem.net)编写,并许可在BSD 2-Clause许可证下。
早期版本中的更改
从2.1.1到3.0
- PHP7 兼容性。PHP7 添加了一些保留字,这些保留字与该库中通用的类名冲突,例如“String”或“Float”。因此,所有类型和转换类都添加了“Type”或“Transformer”后缀。如果某些客户端代码添加了自定义类型,这将是一个不向后兼容的更改(因此从版本2.1.1跃升至版本3),在这种情况下,必须在类型的类名中添加“Type”后缀。如果没有使用自定义类型,版本3在公共API方面应该完全向后兼容。
从2.1到2.1.1
- 类型
Text
和String
已标准化(即:如果安装了intl
扩展,则通过Normalizer::normalize()
处理)。
从2.0到2.1
- 添加了
Json
类型,该类型仅验证给定的字符串是否可以解码为JSON。
从1.2到2.0
- 为了验证依赖项,不再需要
validateObject()
。相反,一切都将自动验证。 - 添加了
validateProperty
方法,该方法作用于属性当前值。此方法可以提供一个约束数组而不是使用注解。可以以约束数组作为第二个参数调用validateObject()
。 - 支持
ArrayCollection
内的依赖项。这些依赖项必须使用两个美元字符定义。例如:IF NOT $$arraykey
。 - 更改了使用具有
Blank
或Mandatory
约束的依赖项时定义标签的方式。