验证是在Web应用程序中一种很常见的任务。在表单中输入数据时需要被验证。数据还需要在写入到数据库或传送到Web服务时进行验证。
Symfony2 配备了一个Validator 组件,它让校验工作变得简单易懂。该组件是基于JSR303 Bean校验规范。
验证的基础知识
了解验证的最好方法就是看它在行动。开始,假设在你的应用程序中创建一个原始的php对象:
// src/AppBundle/Entity/Author.php
namespace AppBundleEntity;
class Author
{
public $name;
}
到目前为止,这只是给你程序提供某些目的的普通类。验证的目的就是要告诉你,一个对象的数据是有效的。为了这个目的,你需要配置一个对象必须遵守规则或者约束列表来让自己的数据合法。这些规则可以通过许多不同的格式(YAML,XML、注释或PHP)来指定。
比如,我们保证属性$name不能为空,来添加下面的规则:
// src/AppBundle/Entity/Author.php
// ...
use SymfonyComponentValidatorConstraints as Assert;
class Author
{
/**
* @AssertNotBlank()
*/
public $name;
}
Protected和private属性以及getter方法也都可以被校验。
使用验证服务
接下来,使用validator服务(class Validator)的validate方法来真正的校验Author对象。 validator的工作很简单:读取一个类的约束规则来校验一个对象的数据是否符合这些规则约束。如果校验失败,一个错误数组(class ConstraintViolationList)将被返回。现在我们在一个controller中来执行它:
// ...
use SymfonyComponentHttpFoundationResponse;
use AppBundleEntityAuthor;
// ...
public function authorAction()
{
$author = new Author();
// ... do something to the $author object
$validator = $this->get('validator');
$errors = $validator->validate($author);
if (count($errors) > 0) {
/*
* Uses a __toString method on the $errors variable which is a
* ConstraintViolationList object. This gives us a nice string
* for debugging.
*/
$errorsString = (string) $errors;
return new Response($errorsString);
}
return new Response('The author is valid! Yes!');
}
如果$name属性是空的,你会看到一个错误信息:
AppBundleAuthor.name:
This value should not be blank
如果你为$name属性插入一个值,那么你会获得快乐的成功信息。
大多数时候,你不需要直接跟validator服务交流或者根本不需要担心打印出错误来。大多数情况下,你将在处理提交表单数据时,间接使用校验。想了解更多请阅读(验证与表单).
你也可以传递一个错误信息集合到一个模版:
if (count($errors) > 0) {
return $this->render('author/validation.html.twig', array(
'errors' => $errors,
));
}
在模版中,你可以根据需要精确的输出错误列表:
{# app/Resources/views/author/validation.html.twig #}
验证和表单
validator服务可以在任何时候使用,来验证任何对象。 事实上,你将经常在处理表单时,间接使用validator。Symfony的表单类库间接使用validator服务来在数据被提交和绑定后验证底层对象。对象违反约束信息将被转化到FieldError对象,该对象可以很容易的被展示在你的表单中。在一个controller中的传统表单提交流程
// ...
use AppBundleEntityAuthor;
use AppBundleFormAuthorType;
use SymfonyComponentHttpFoundationRequest;
// ...
public function updateAction(Request $request)
{
$author = new Author();
$form = $this->createForm(new AuthorType(), $author);
$form->handleRequest($request);
if ($form->isValid()) {
// the validation passed, do something with the $author object
return $this->redirectToRoute(...);
}
return $this->render('author/form.html.twig', array(
'form' => $form->createView(),
));
}
该实例使用一个 AuthorType表单类,更多信息请查看表单章。
配置
Symfony2 的validator默认情况下是启用的。但是如果你使用了annotations方法来指定你的约束,那么你需要显式的开启annotations功能:
# app/config/config.yml
framework:
validation: { enable_annotations: true }
约束规则
Validator是设计了的目的是用来按照约束规则验证对象的。为了验证一个对象,只需要映射一个或者多个约束到它要验证的类,然后把它传递给validator服务即可。
本质上,一个约束就是一个简单的PHP对象,它可以生成一个决断语句。 在现实生活中,一个约束可以是”蛋糕不能烤焦了” 这样的规则约束。在Symfony2中,约束都差不多:他们断言某个条件是否成立。给定一个值,约束会告诉你这个值是否遵守了你的约束规则。
支持的约束
symfony封装了许多最常用的约束:
基础的约束
首先是基础约束规则:使用他们来决断非常基本的事,比如你对象属性的值或者方法的返回值。
NotBlank
Blank
NotNull
Null
True
False
Type
字符串约束
Email
Length
Url
Regex
Ip
Uuid
数字约束¶
Range
比较约束
EqualTo
NotEqualTo
IdenticalTo
NotIdenticalTo
LessThan
LessThanOrEqual
GreaterThan
GreaterThanOrEqual
日期约束
Date
DateTime
Time
集合约束
Choice
Collection
Count
UniqueEntity
Language
Locale
Country
文件约束
File
Image
金融和其他数字约束
CardScheme
Currency
Luhn
Iban
Isbn
Issn
其他约束
Callback
Expression
All
UserPassword
Valid
你也可以创建自己的自定义约束。请查看cookbook的”How to Create a custom Validation Constraint“。
约束的配置
一些约束,比如NotBlank,很简单,但是其它的比如Choice约束,有许多配置项需要设置。假设Author类有另外一个属性,gender可以被设置为”male”或者”female”:
// src/AppBundle/Entity/Author.php
// ...
use SymfonyComponentValidatorConstraints as Assert;
class Author
{
/**
* @AssertChoice(
* choices = { "male", "female" },
* message = "Choose a valid gender."
* )
*/
public $gender;
// ...
}
一个约束的选项通常都是通过一个数组来传递的。有些约束也允许你通过一个值指定,”default”数组是可以替换的。在Choice约束时,choices选项就可以通过这种方式指定。
// src/AppBundle/Entity/Author.php
// ...
use SymfonyComponentValidatorConstraints as Assert;
class Author
{
/**
* @AssertChoice({"male", "female"})
*/
protected $gender;
// ...
}
这纯粹是让最常见的配置选项用起来更加简单和快速。
如果你不确定如何指定一个配置,你可以检查api文档或者为了保险起见你还是通过数组配置吧(上面第一种方式)。
翻译约束信息
更多约束的翻译请查看 Translating Constraint Messages.
约束目标
约束可以被用于一个类的属性或者一个公共的getter方法。属性约束最常用也最简单,而公共的getter方法约束则允许你指定一个复杂的约束规则。
属性约束
属性验证一个最常规的验证技术。Symfony2允许你校验private,protected或者public属性。下面代码显示如何配置Author对象的$firstName属性至少有3个字符
// AppBundle/Entity/Author.php
// ...
use SymfonyComponentValidatorConstraints as Assert;
class Author
{
/**
* @AssertNotBlank()
* @AssertLength(min=3)
*/
private $firstName;
}
Getters约束
约束也可以应用于一个方法的返回值。Symfony2 允许你添加一个约束到任何”get”、”is”或者”has”开头的public方法。这种类型的方法被称为“getters”。
has开头的方法在symfony2.5被引入。
该技术的好处是允许你动态的校验你的对象。比如,假设你想确认密码字段不匹配用户的first name(因为安全原因)。你可以通过创建一个isPasswordLegal 方法,然后决断这个方法必须返回true:
// src/AppBundle/Entity/Author.php
// ...
use SymfonyComponentValidatorConstraints as Assert;
class Author
{
/**
* @AssertTrue(message = "The password cannot match your first name")
*/
public function isPasswordLegal()
{
// ... return true or false
}
}
现在我们创建一个isPasswordLegal()方法,并且包含你需要逻辑:
public function isPasswordLegal()
{
return $this->firstName !== $this->password;
}
眼尖的人可能会注意到getter的前缀(“get”或者”is”)在映射时被忽略了。这允许你在不改变验证规则的前提下,把一个约束移动到一个具有同名属性上,反之亦然。
类约束
有一些约束可以应用到被验证的整个类。例如,回调(Callback)类型的约束,就是一个通用的,用于类自身的约束。当类被验证之后,约束所指定的方法将被直接执行,以便提供更多的自定义验证。
验证组
到目前为止,你已经能够添加约束到类,并询问是否该类传入所有定义的约束规则。一些情况下,你只需要使用该类的其中某些规则来验证一个对象。要做到这些,你可以组织每一个约束到一个或者多个验证组中,然后应用使用其中一组验证。
比如,假设你有一个User类,它会在用户注册和用户更新他们的联系信息时使用:
// src/AppBundle/Entity/User.php
namespace AppBundleEntity;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentValidatorConstraints as Assert;
class User implements UserInterface
{
/**
* @AssertEmail(groups={"registration"})
*/
private $email;
/**
* @AssertNotBlank(groups={"registration"})
* @AssertLength(min=7, groups={"registration"})
*/
private $password;
/**
* @AssertLength(min=2)
*/
private $city;
}
在这个配置中,有三个验证组
Default
包含当前类的约束和所有没有分配到任何组的约束规则
User
等于在Default组中的User对象所有的约束。他总是以类的名称命名。它和default组的不同如下所示。
registration
只包含了email和password字段的校验规则
默认组中的约束是一个没有明确组配置的类的约束,或者是被配置为一组等于类名Default字符串的约束。
当只是User对象验证的时候,没有Default组和User组的差别。但是,如果User嵌入了对象就有区别了。例如,User有一个address属性,它包含很多的Address对象,并且你要添加有效的约束到这个属性,所以当你验证User对象时,你就要对他进行验证。
如果你验证User使用Default组,Address类将使用Default组约束。但是,如果你验证的User使用这个User验证组,那么address类将被User组验证。
换句话说,在default组和类名组(例如User)是一样的,除了在类被嵌入到另一个对象时,会根据另一个类的约束验证。
如果你有继承(如,User extends BaseUser)并且你去验证子类的类名(如User),那么所有User和BaseUser的约束都将被验证。但是,如果你验证使用基类(如BaseUser),那么只有默认约束和BaseUser类被验证;
告诉validator使用指定的验证组,传一个或者多个组名作为validate()方法的第三个参数即可:
// If you're using the new 2.5 validation API (you probably are!)
$errors = $validator->validate($author, null, array('registration'));
// If you're using the old 2.4 validation API, pass the group names as the second argument
// $errors = $validator->validate($author, array('registration'));
如果你没有指定组,所有的约束都属于Default.
当然,你通常是间接验证表单库,如何在表单使用验证组请看Validation Groups.
约束组顺序
在某些情况下,需要按照步骤验证你的组。要做到这一点,你可以使用GroupSequence功能。在这种情况下,给一个对象定义一组顺序,也就是确定组验证的顺序。
例如,假设你有一个User类,并希望他验证用户名和密码不能重复,只有在其他验证都通过后在验证(为了避免多个错误消息)。
// src/AppBundle/Entity/User.php
namespace AppBundleEntity;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentValidatorConstraints as Assert;
/**
* @AssertGroupSequence({"User", "Strict"})
*/
class User implements UserInterface
{
/**
* @AssertNotBlank
*/
private $username;
/**
* @AssertNotBlank
*/
private $password;
/**
* @AssertTrue(message="The password cannot match your username", groups={"Strict"})
*/
public function isPasswordLegal()
{
return ($this->username !== $this->password);
}
}
在这个案例中,他会先验证所有验证中的User组(就好像以前的Default组一样)。只有当这组所有约束都通过时,才会进行第二组验证,strict。
正如你已经从上节中看到的,default组和类名组(如User组)都是相同的。然而,使用组顺序时,他们不再相同了。Default组将引用组序列,而不是所有不属于任何群体的约束。
这意味着,当你指定一组顺序,你必须要使用 {ClassName}(例如User)组。当你使用Default,你会得到一个无线递归。(default组引用组顺序,其中将包含default,他会引用同组的组顺序,...组)。
约束组顺序Providers
想象一下一个User实体可以是一个普通的用户也可以是一个高级用户。当他是一个高级用户,user实体应该添加一些额外的约束(例如 信用卡详细信息)。去动态的确认哪个组被激活,你需要创建一个验证组顺序Providers。首先,创建实体并给他一个新的约束组Premium:
// src/AppBundle/Entity/User.php
namespace AppBundleEntity;
use SymfonyComponentValidatorConstraints as Assert;
class User
{
/**
* @AssertNotBlank()
*/
private $name;
/**
* @AssertCardScheme(
* schemes={"VISA"},
* groups={"Premium"},
* )
*/
private $creditCard;
// ...
}
现在,改写User类并实现 GroupSequenceProviderInterface并且添加getGroupSequence()方法,他能以一个数组形式返回我们使用的组。
// src/AppBundle/Entity/User.php
namespace AppBundleEntity;
// ...
use SymfonyComponentValidatorGroupSequenceProviderInterface;
class User implements GroupSequenceProviderInterface
{
// ...
public function getGroupSequence()
{
$groups = array('User');
if ($this->isPremium()) {
$groups[] = 'Premium';
}
return $groups;
}
}
最后,你必须告诉你的验证组件,你的User类使用组顺序进行验证:
// src/AppBundle/Entity/User.php
namespace AppBundleEntity;
// ...
/**
* @AssertGroupSequenceProvider
*/
class User implements GroupSequenceProviderInterface
{
// ...
}
值和数组的验证
到目前为止,你已经看到了如何验证整个对象。但有时,你只是想验证一个简单的数值 – 想验证一个字符串是一个有效的电子邮件地址。这其实是很容易做到的。从控制器里面,它看起来像这样:
// ...
use SymfonyComponentValidatorConstraints as Assert;
// ...
public function addEmailAction($email)
{
$emailConstraint = new AssertEmail();
// all constraint "options" can be set this way
$emailConstraint->message = 'Invalid email address';
// use the validator to validate the value
// If you're using the new 2.5 validation API (you probably are!)
$errorList = $this->get('validator')->validate(
$email,
$emailConstraint
);
// If you're using the old 2.4 validation API
/*
$errorList = $this->get('validator')->validateValue(
$email,
$emailConstraint
);
*/
if (0 === count($errorList)) {
// ... this IS a valid email address, do something
} else {
// this is *not* a valid email address
$errorMessage = $errorList[0]->getMessage();
// ... do something with the error
}
// ...
}
通过调用validator的validate方法,你可以传入一个原始值和一个你要使用的校验对象。可用约束完整列表-以及每个约束的完整类名-查看 constraints reference章。
该validate方法会返回一个ConstraintViolationList对象,它扮演的只是一个错误信息数组的角色。集合中的每一个错误是一个ConstraintViolation对象,使用对象的getMessage方法可以获取错误信息。
总结:
Symfony2 的validator是一个强大的工具,它可以被用来保证任何对象数据的合法性。它的强大来源于约束规则,你可以把它们应用于你对象的属性和getter方法。其实,你大多数情况下都是在使用表单时,间接的应用了验证框架,记住它可以被应用于任何地方验证任何对象。