
校驗在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方法。其實,你大多數情況下都是在使用表單時,間接的應用了校驗框架,記住它可以被應用於任何地方校驗任何對象。
