校验在web应用程序中是一个常见的任务。数据输入到表单需要被校验。数据在被写入数据库之前或者传入一个webservice时也需要被校验。
Symfony2 配备了一个Validator 组件,它让校验工作变得简单易懂。该组件是基于JSR303 Bean校验规范。一个Java规范用在PHP中。
基本验证
理解校验的最好方法是看它的表现。首先,假设你已经创建了一个用于你应用程序某个地方的PHP对象。
//src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity; class Author { public $name; }
到现在为止,它只是个服务于你应用程序的某些目的的普通的类。而校验的目的就是要告诉你对象的数据是否合法。为了这个目的,你需要配置一个对象必须遵守规则或者约束列表来让自己的数据合法。这些规则可以被描述成多种不同的格式的(比如,YAML,XML,类声明或者PHP)。比如,我们保证属性$name不能为空,来添加下面的规则:
YAML格式:
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: name: - NotBlank: ~
类声明格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank() */ public $name; }
XML格式:
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="Acme\BlogBundle\Entity\Author"> <property name="name"> <constraint name="NotBlank" /> </property> </class> </constraint-mapping>
PHP代码格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; class Author { public $name; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('name', new NotBlank()); } }
Protected和private属性以及getter方法也都可以被校验。
使用validator服务:
接下来,使用validator服务的validate方法来真正的校验Author对象。 validator的工作很简单:读取一个类的约束规则来校验一个对象的数据是否符合这些规则约束。如果校验失败,一个错误数组将被返回。现在我们在一个controller中来执行它:
use Symfony\Component\HttpFoundation\Response; use Acme\BlogBundle\Entity\Author; //... public function indexAction() { $author = new Author(); //... 对$auother对象做些什么 $validator = $this->get('validator'); $errors = $validator->validate($author); if(count($errors) >0){ return new Response(print_r($errors, true)); }else{ return new Response('The author is valid! Yes!'); } }
如果$name 属性为空,你将看到下面的错误信息:
Acme\BlogBundle\Author.name: This value should not be blank
如果你为$name属性插入一个值,那么你会获得快乐的成功信息。
大多数时候,你不需要直接跟validator服务交流或者根本不需要担心打印出错误来。
大多数情况下,你将在处理提交表单数据时间接使用校验。
你也可以传递一个错误信息集合到一个模版:
if(count($errors)>0){ return $this->render('AcmeBlogBundle:Author:validate.html.twig',array( 'errors' => $errors, )); }else{ //... }
在模版中,你可以根据需要精确的输出错误列表:
Twig格式:
{# src/Acme/BlogBundle/Resources/views/Author/validate.html.twig #} <h3>The author has the following errros</h3> <ul> {% for error in errors %} <li>{{ error.message }}</li> {% endfor %} </ul>
校验和表单
validator服务可以被用于任何时候校验任何对象。 事实上,你将经常在处理表单时间接使用validator。Symfony的表单类库间接使用validator服务来在数据被提交和绑定后校验底层对象。对象违反约束信息将被转化到FieldError对象,该对象可以很容易的被展示在你的表单中。在一个controller中的传统表单提交流程如下:
use Acme\BlogBundle\Entity\Author; use Acme\BlogBundle\Form\AuthorType; use Acme\Component\HttpFoundation\Request; //... public function updateAction(Request $request) { $author = new Acme\BlogBundle\Entity\Author(); $form = $this->createForm(new AuthorType(),$author); if($request->getMethod() =='POST'){ $form->bindRequest($request); if($form->isvalid()){ //对$author做一些操作 return $this->redirect($this->generateUrl('...')); } } return $this->render('BlogBundle:Author:form.html.twig',array( 'form' => $form->createView(), )); }
配置:
Symfony2 的validator默认情况下是可用的。但是如果你使用了生命方法来指定你的约束,那么你需要显式的开启声明功能:
YAML格式:
# app/config/config.yml framework: validation: {enable_annotations: true }
XML格式:
<!-- app/config/config.xml --> <framework:config> <framework:validation enable-annotations="true" /> </framework:config>
PHP代码格式:
// app/config/config.php $contianer->loadFromExtension('framework',array('validation'=> array( 'enable_annotations'=>true, )));
约束规则
Validator是设计了用来按照约束规则校验对象的。为了校验一个对象,只需要映射一个或者多个约束到它要校验的类然后把它传递给validator服务即可。
本质上,一个约束就是一个简单的PHP对象,它可以生成一个决断语句。 在现实生活中,一个约束可以是"蛋糕不能烤焦了" 这样的规则约束。在Symfony2中,约束都差不多:他们决断某个条件是否成立。给定一个值,约束会告诉你这个值是否遵守了你的约束规则。
Symfony2 支持的约束规则
首先是基础约束规则:使用他们来决断非常基本的事,比如你对象属性的值或者方法的返回值。
NotBlank,Blank,NotNull,Null,True,False,Type
字符串约束:Email,MinLength,MaxLength,Url,Regex,Ip等
数字约束:Max,Min
日期约束:Date,DateTime和Time
集合约束:Choice,Collection,UniqueEntity,Language,Locale和Country等。
文件约束:File,Image
其它约束:Callback,All,Valid
你也可以创建自己的自定义约束。
约束配置:
一些约束,比如NotBlank,很简单,但是其它的比如Choice约束,有许多配置项需要设置。假设Author类有另外一个属性,gener可以被设置为”male"或者"female":
YAML格式:
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: gener: - Choice: { choices: [male, female], message: Choos a valid gender. }
类声明格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\Choice( * choices = {"male","female"}, * message = "Choose a valid gender." * ) */ public $gender; }
XML格式:
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="Acme\BlogBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <option name="choices"> <value>male</value> <value>female</value> </option> <option name="message">Choose a valid gender.</option> </constraint> </property> </class> </constraint-mapping>
PHP代码格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; class Author { public $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Choice(array( 'choices' => array('male', 'female'), 'message' => 'Choose a valid gender.', ))); } }
一个约束的选项通常都是通过一个数组来传递的。有些约束也允许你传递一个值。"default"在数组中是可选的。在Choice约束时,choices选项就可以通过这种方式指定。
YAML格式:
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: gender: - Choice: [male, female]
类声明格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\Choice({"male", "female"}) */ protected $gender; }
XML格式:
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="Acme\BlogBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <value>male</value> <value>female</value> </constraint> </property> </class> </constraint-mapping>
PHP格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Choice; class Author { protected $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Choice(array('male', 'female'))); } }
约束目标
约束可以被用于一个类的属性或者一个公共的getter方法。属性约束最常用也最简单,而公共的getter方法约束则允许你指定一个复杂的约束规则。
属性约束:
校验类的属性石一个最常规的校验技术。Symfony2允许你校验private,protected或者public属性。下面代码显示如何配置Author对象的$firstName属性至少有3个字符:
YAML格式:
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: firstName: - NotBlank: ~ - MinLength: 3
类声明格式:
// Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank() * @Assert\MinLength(3) */ private $firstName; }
XML格式:
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\Author"> <property name="firstName"> <constraint name="NotBlank" /> <constraint name="MinLength">3</constraint> </property> </class>
PHP代码格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\MinLength; class Author { private $firstName; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('firstName', new NotBlank()); $metadata->addPropertyConstraint('firstName', new MinLength(3)); } }
Getters
约束也可以应用于一个方法的返回值。Symfony2 允许你添加一个约束到任何"get"或者 "is"开头的public方法。该技术的好处是允许你动态的校验你的对象。比如,假设你想确认密码字段不匹配用户的first name(因为安全原因)。你可以通过创建一个idPasswordLegal 方法,然后决断这个方法必须返回true:
YAML格式:
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: getters: passwordLegal: - "True": { message: "The password cannot match your first name" }
类声明格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\True(message = "The password cannot match your first name") */ public function isPasswordLegal() { // return true or false } }
XML格式:
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\Author"> <getter property="passwordLegal"> <constraint name="True"> <option name="message">The password cannot match your first name</option> </constraint> </getter> </class>
PHP代码格式:
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\True; class Author { public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addGetterConstraint('passwordLegal', new True(array( 'message' => 'The password cannot match your first name', ))); } }
现在我们创建一个isPasswordLegal()方法,并且包含你需要逻辑:
public function isPasswordLegal() { return ($this->firstName != $this->password); }
眼尖的人可能会注意到getter的前缀("get"或者"is")在映射时被忽略了。这允许你在不改变校验规则的前提下,把一个约束移动到一个具有同名属性上,反之亦然。
类:
一些约束应用到整个类被校验上面。比如,Callback约束是一个通用约束,它可以应用到类自身。当类被校验时,被约束描述的方法只是被执行这样每一个可以提供更个性化的校验。
校验分组
到目前为止,你已经能够添加约束到类并询问是否该类传入所有定义的约束规则。一些情况下,你只需要使用该类的其中某些规则来校验一个对象。要做到这些,你可以组织每一个约束到一个或者多个校验组中,然后应用使用其中一组校验。比如,假设你有一个User类,它会在用户注册和用户更新他们的联系信息时使用。
YAML格式:
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\User: properties: email: - Email: { groups: [registration] } password: - NotBlank: { groups: [registration] } - MinLength: { limit: 7, groups: [registration] } city: - MinLength: 2
类声明格式:
// src/Acme/BlogBundle/Entity/User.php namespace Acme\BlogBundle\Entity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; class User implements UserInterface { /** * @Assert\Email(groups={"registration"}) */ private $email; /** * @Assert\NotBlank(groups={"registration"}) * @Assert\MinLength(limit=7, groups={"registration"}) */ private $password; /** * @Assert\MinLength(2) */ private $city; }
XML格式:
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\User"> <property name="email"> <constraint name="Email"> <option name="groups"> <value>registration</value> </option> </constraint> </property> <property name="password"> <constraint name="NotBlank"> <option name="groups"> <value>registration</value> </option> </constraint> <constraint name="MinLength"> <option name="limit">7</option> <option name="groups"> <value>registration</value> </option> </constraint> </property> <property name="city"> <constraint name="MinLength">7</constraint> </property> </class>
PHP代码格式:
// src/Acme/BlogBundle/Entity/User.php namespace Acme\BlogBundle\Entity; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\MinLength; class User { public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('email', new Email(array( 'groups' => array('registration') ))); $metadata->addPropertyConstraint('password', new NotBlank(array( 'groups' => array('registration') ))); $metadata->addPropertyConstraint('password', new MinLength(array( 'limit' => 7, 'groups' => array('registration') ))); $metadata->addPropertyConstraint('city', new MinLength(3)); } }
这里我们配置了两个校验组:
default默认组: 包括所有没有分配到任何组的约束规则
registration: 只包含了email和password字段的校验规则
告诉validator使用指定的校验组,传一个或者多个组名作为validate()方法的第二个参数即可:
$errors = $validator->validate($author,array('registration'));
值和数组校验
到目前为止,我们已经看了如何校验整个对象。但是有时候,我们可能想值校验一个单独的值,比如校验一个字符串是不是一个合法的email地址。这非常简单,在Controller类中进行如下:
// 在controller类前引用相应的校验命名空间 use Symfony\Component\Validator\Constraints\Email; public function addEmailAction($email) { $emailConstraint = new Email(); // 所有的校验选项(options)都可以这样设置 $emailConstraint->message = 'Invalid email address'; // 使用validator来校验一个值 $errorList = $this->get('validator')->validateValue($email, $emailConstraint); if (count($errorList) == 0) { // 这是一个合法的email地址,可以做些什么 } else { // 这是一个非法的email地址 $errorMessage = $errorList[0]->getMessage() // 做一些错误处理 } // ... }
通过调用validator的validateValue方法,你可以传入一个原始值和一个你要使用的校验对象。该方法会返回一个ConstraintViolationList对象,它扮演的只是一个错误信息数组的角色。集合中的每一个错误是一个ConstraintViolation对象,使用对象的getMessage方法可以获取错误信息。
总结:
Symfony2 的validator是一个强大的工具,它可以被用来保证任何对象数据的合法性。它的强大来源于约束规则,你可以把它们应用于你对象的属性和getter方法。其实,你大多数情况下都是在使用表单时,间接的应用了校验框架,记住它可以被应用于任何地方校验任何对象。