驗(yàn)證(Validation)
驗(yàn)證(Validation )是在Web程序中極為常見的任務(wù)。表單中輸入的數(shù)據(jù)需要驗(yàn)證。寫入到數(shù)據(jù)庫或傳送到Web服務(wù)時(shí),數(shù)據(jù)也需要驗(yàn)證。
Symfony自帶了一個(gè) Validator 組件,它讓校驗(yàn)工作變得簡單和透明。該組件基于 JSR303 Bean校驗(yàn)規(guī)范。
驗(yàn)證的基礎(chǔ) ?
理解驗(yàn)證的最好方法就是看它的實(shí)際應(yīng)用。在開始之前,假設(shè)你創(chuàng)建了一個(gè)原生php對象,它用在程序需要的某個(gè)地方:
// src/AppBundle/Entity/Author.phpnamespace AppBundle\Entity; class Author{ public $name;}
目前為止,這只是一個(gè)為你的程序提供某些用途的普通類。驗(yàn)證的目的就是要告訴你,對象中的數(shù)據(jù)是否有效。為此,你需要配置一個(gè)規(guī)則列表(稱為 constraints/約束 ),對象必須遵循之方能有效。這些規(guī)則可以通過多種不同的格式(YAML、XML、annotations或PHP)來指定。
比如,要保證屬性 $name
不為空,添加以下內(nèi)容:
PHP:// src/AppBundle/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()); }}
XML:<!-- src/AppBundle/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="AppBundle\Entity\Author"> <property name="name"> <constraint name="NotBlank" /> </property> </class></constraint-mapping>
YAML:# src/AppBundle/Resources/config/validation.ymlAppBundle\Entity\Author: properties: name: - NotBlank: ~
Annotations:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Constraints as Assert; class Author{ /** * @Assert\NotBlank() */ public $name;}
Protected和private屬性以及“getter”方法也可以被驗(yàn)證(見 約束的投放范圍)。
使用驗(yàn)證服務(wù) ?
接下來,要真正的校驗(yàn) Author
對象,使用 validator
服務(wù)(Validator
類)的 validate
方法。 validator
的工作很簡單:讀取一個(gè)類的約束規(guī)則來校驗(yàn)對象數(shù)據(jù)是否滿足這些約束。如果驗(yàn)證失敗,一個(gè)非空的錯(cuò)誤列表(ConstraintViolationList
類)將被返回。在控制器中實(shí)踐這個(gè)簡單例子:
// ...use Symfony\Component\HttpFoundation\Response;use AppBundle\Entity\Author; // ...public function authorAction(){ $author = new Author(); // ... do something to the $author object // ... 對 $author 對象做一些事 $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. * 對 $errors 變量,即 ConstraintViolationList 對象,使用 __toString 方法。 * 這給了我們一個(gè)美觀的字符串用于調(diào)試。 */ $errorsString = (string) $errors; return new Response($errorsString); } return new Response('The author is valid! Yes!');}
如果 $name
屬性是空的,你會(huì)看到一個(gè)錯(cuò)誤信息:
1 2 | AppBundle\Author.name:
This value should not be blank |
如果你為 name
屬性插入一個(gè)值,令人高興的成功信息就會(huì)出現(xiàn)。
多數(shù)時(shí)候,你不需要直接跟 validator
服務(wù)互動(dòng),或者毋須為輸出錯(cuò)誤信息擔(dān)心。多數(shù)情況下,你在處理提交過來的表單數(shù)據(jù)時(shí),間接地使用validation驗(yàn)證。參考 驗(yàn)證與表單 以了解更多。
你也可以傳遞“錯(cuò)誤信息集合”(collection of errors)到模版中:
if (count($errors) > 0) { return $this->render('author/validation.html.twig', array( 'errors' => $errors, ));}
在模版中,你可以根據(jù)需要精確輸出錯(cuò)誤列表:
PHP:<!-- app/Resources/views/author/validation.html.php --> <h3>The author has the following errors</h3> <ul><?php foreach ($errors as $error): ?> <li><?php echo $error->getMessage() ?></li><?php endforeach ?></ul>
Twig:{# app/Resources/views/author/validation.html.twig #}<h3>The author has the following errors</h3> <ul>{% for error in errors %} <li>{{ error.message }}</li>{% endfor %}</ul>
每一個(gè)驗(yàn)證錯(cuò)誤(被稱為“constraint violation/約束違反”)都由一個(gè) ConstraintViolation
對象來呈現(xiàn)。
配置 ?
Symfony的validator是默認(rèn)開啟的,但是如果你使用annotation方式來指定約束,必須顯式地開啟(用于驗(yàn)證的)annotation:
PHP:// app/config/config.php$container->loadFromExtension('framework', array( 'validation' => array( 'enable_annotations' => true, ),));
XML:<!-- app/config/config.xml --><?xml version="1.0" encoding="UTF-8" ?><container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <framework:validation enable-annotations="true" /> </framework:config></container>
YAML:# app/config/config.ymlframework: validation: { enable_annotations: true }
約束規(guī)則 ?
Validator
被設(shè)計(jì)成針對 約束(即規(guī)則)來驗(yàn)證對象。要驗(yàn)證一個(gè)對象,只需把一或多個(gè)約束映射到它要驗(yàn)證的類,然后再把它傳遞給 validator
服務(wù)即可。
在幕后,一個(gè)約束就是一個(gè)PHP對象,它可以生成一個(gè)用于決斷的聲明。在實(shí)踐中,一個(gè)約束可以是“蛋糕不能燒”。在Symfony中,約束是類似的:它們就是“條件是否為真”的斷言。給定一個(gè)值,約束會(huì)告訴你這個(gè)值是否遵守了你的約束規(guī)則。
支持的約束 ?
Symfony封裝了很多最常用的約束:
基本約束 ?
這些是基本的約束:使用它們來斷言屬性值相關(guān)的非常基礎(chǔ)的東西,或者斷言你程序中的方法之返回值。
字符串約束 ?
數(shù)字約束 ?
比較約束 ?
日期約束 ?
Collection約束 ?
文件約束 ?
財(cái)務(wù)數(shù)字約束 ?
其他約束 ?
你也可以創(chuàng)建自己的自定義約束。如何創(chuàng)建自定義的驗(yàn)證約束 一文覆蓋了此話題。
約束的配置 ?
一些約束,像是 NotBlank 較為簡單,而其他一些像是 Choice 約束,有許多可用的配置選項(xiàng)。假設(shè) Author
類有另外一個(gè)屬性叫 gender
,該屬性可以被設(shè)置為“male”、“female” 或 “other”:
PHP:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Mapping\ClassMetadata;use Symfony\Component\Validator\Constraints as Assert; class Author{ public $gender; // ... public static function loadValidatorMetadata(ClassMetadata $metadata) { // ... $metadata->addPropertyConstraint('gender', new Assert\Choice(array( 'choices' => array('male', 'female', 'other'), 'message' => 'Choose a valid gender.', ))); }}
XML:<!-- src/AppBundle/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="AppBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <option name="choices"> <value>male</value> <value>female</value> <value>other</value> </option> <option name="message">Choose a valid gender.</option> </constraint> </property> <!-- ... --> </class></constraint-mapping>
YAML:# src/AppBundle/Resources/config/validation.ymlAppBundle\Entity\Author: properties: gender: - Choice: { choices: [male, female, other], message: Choose a valid gender. } # ...
Annotations:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Constraints as Assert; class Author{ /** * @Assert\Choice( * choices = { "male", "female", "other" }, * message = "Choose a valid gender." * ) */ public $gender; // ...}
約束的選項(xiàng)始終可以通過一個(gè)數(shù)組來傳遞的。有些約束也允許你傳入一個(gè) ”default” 選項(xiàng)的值來代替這個(gè)數(shù)組。在 Choice
約束中,choices
選項(xiàng)就可以通過這種方式指定。
PHP:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Mapping\ClassMetadata;use Symfony\Component\Validator\Constraints as Assert; class Author{ protected $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { // ... $metadata->addPropertyConstraint( 'gender', new Assert\Choice(array('male', 'female', 'other')) ); }}
XML:<!-- src/AppBundle/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="AppBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <value>male</value> <value>female</value> <value>other</value> </constraint> </property> <!-- ... --> </class></constraint-mapping>
YAML:# src/AppBundle/Resources/config/validation.ymlAppBundle\Entity\Author: properties: gender: - Choice: [male, female, other] # ...
Annotations:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Constraints as Assert; class Author{ /** * @Assert\Choice({"male", "female", "other"}) */ protected $gender; // ...}
這純粹是為了讓最常見的配置選項(xiàng)在用起來時(shí)更加的簡單快速。
如果你不確定如何指定一個(gè)選項(xiàng),要么去查看API文檔,要么為了保險(xiǎn)起見,通過一個(gè)選項(xiàng)數(shù)組來傳入(即上面第一種方式)。
約束的目標(biāo) ?
約束可以被應(yīng)用到類的屬性(如 name
)或者一個(gè)公共的getter方法(如 getFullName
)乃至整個(gè)類上。屬性約束最常用也最簡單,而Getter約束則允許你指定更加復(fù)雜的驗(yàn)證規(guī)則。最后,如果,類約束的使用場景是,你要將類作為整體進(jìn)行驗(yàn)證。
屬性約束 ?
類屬性的驗(yàn)證是一個(gè)最基本的驗(yàn)證技巧。Symfony允許你校驗(yàn) private, protected 或者 public 屬性。下面代碼展示了如何配置 Author
對象的 $firstName
屬性,令其至少有3個(gè)字符:
PHP:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Mapping\ClassMetadata;use Symfony\Component\Validator\Constraints as Assert; class Author{ private $firstName; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('firstName', new Assert\NotBlank()); $metadata->addPropertyConstraint( 'firstName', new Assert\Length(array("min" => 3)) ); }}
XML:<!-- src/AppBundle/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="AppBundle\Entity\Author"> <property name="firstName"> <constraint name="NotBlank" /> <constraint name="Length"> <option name="min">3</option> </constraint> </property> </class></constraint-mapping>
YAML:# src/AppBundle/Resources/config/validation.ymlAppBundle\Entity\Author: properties: firstName: - NotBlank: ~ - Length: min: 3
Annotations:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Constraints as Assert; class Author{ /** * @Assert\NotBlank() * @Assert\Length(min=3) */ private $firstName;}
Getters約束 ?
約束也可以應(yīng)用于方法的返回值。Symfony允許你把一個(gè)約束添加到任何“get”、“is” 或者 “has”開頭的public方法。這一類方法被稱為“getters”。
這種技巧的好處是允許你動(dòng)態(tài)驗(yàn)證你的對象。例如,假設(shè)你想確保密碼字段不能匹配到用戶的firstname(出于安全原因)。你可以通過創(chuàng)建一個(gè) isPasswordLegal
方法,然后斷言該方法必須返回 true
來實(shí)現(xiàn):
PHP:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Mapping\ClassMetadata;use Symfony\Component\Validator\Constraints as Assert; class Author{ public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addGetterConstraint('passwordLegal', new Assert\IsTrue(array( 'message' => 'The password cannot match your first name', ))); }}
XML:<!-- src/AppBundle/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="AppBundle\Entity\Author"> <getter property="passwordLegal"> <constraint name="IsTrue"> <option name="message">The password cannot match your first name</option> </constraint> </getter> </class></constraint-mapping>
YAML:# src/AppBundle/Resources/config/validation.ymlAppBundle\Entity\Author: getters: passwordLegal: - 'IsTrue': { message: 'The password cannot match your first name' }
Annotations:// src/AppBundle/Entity/Author.php // ...use Symfony\Component\Validator\Constraints as Assert; class Author{ /** * @Assert\IsTrue(message = "The password cannot match your first name") */ public function isPasswordLegal() { // ... return true or false }}
現(xiàn)在,創(chuàng)建一個(gè) isPasswordLegal()
方法,含有你所需的邏輯:
public function isPasswordLegal(){ return $this->firstName !== $this->password;}
眼尖的人可能會(huì)注意到,在YAML, XML和PHP的約束配置格式中,getter的前綴(“get”、”is” 或者 “has”) 在映射時(shí)被忽略了。這能讓你在不改變驗(yàn)證邏輯的前提下,把一個(gè)約束移動(dòng)到后面的一個(gè)同名屬性之上(反之亦然)。
類約束 ?
有一些約束可以應(yīng)用到被驗(yàn)證的整個(gè)類。例如,Callback 類型的約束,就是一個(gè)可以作用到類本身的通用約束。當(dāng)類被驗(yàn)證時(shí),約束所指定的方法將被直接執(zhí)行,以便提供更多的自定義驗(yàn)證。
總結(jié) ?
Symfony的 validator
(驗(yàn)證)是一個(gè)強(qiáng)大的工具,它可以被用來保證任何對象之?dāng)?shù)據(jù)的合法性。它的強(qiáng)大來源自約束規(guī)則(constraints),你可以把它應(yīng)用到對象的屬性和getter方法上。雖然,多數(shù)情況下都是在使用表單時(shí)間接應(yīng)用了驗(yàn)證框架,記得,它可以用在任何地方去驗(yàn)證任何對象。