# 輸入驗證
一般說來,程序猿永遠不應該信任從最終用戶直接接收到的數據,并且使用它們之前應始終先驗證其可靠性。
要給?[model](http://www.yiichina.com/doc/guide/2.0/structure-models)?填充其所需的用戶輸入數據,你可以調用 yii\base\Model::validate() 方法驗證它們。該方法會返回一個布爾值,指明是否通過驗證。若沒有通過,你能通過 yii\base\Model::errors 屬性獲取相應的報錯信息。比如,
~~~
$model = new \app\models\ContactForm;
// 用用戶輸入來填充模型的特性
$model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// 若所有輸入都是有效的
} else {
// 有效性驗證失敗:$errors 屬性就是存儲錯誤信息的數組
$errors = $model->errors;
}
~~~
`validate()`?方法,在幕后為執行驗證操作,進行了以下步驟:
1. 通過從 yii\base\Model::scenarios() 方法返回基于當前 yii\base\Model::scenario 的特性屬性列表,算出哪些特性應該進行有效性驗證。這些屬性被稱作?*active attributes*(激活特性)。
2. 通過從 yii\base\Model::rules() 方法返回基于當前 yii\base\Model::scenario 的驗證規則列表,這些規則被稱作?*active rules*(激活規則)。
3. 用每個激活規則去驗證每個與之關聯的激活特性。若失敗,則記錄下對應模型特性的錯誤信息。
## 聲明規則(Rules)
要讓?`validate()`?方法起作用,你需要聲明與需驗證模型特性相關的驗證規則。為此,需要重寫 yii\base\Model::rules() 方法。下面的例子展示了如何聲明用于驗證?`ContactForm`?模型的相關驗證規則:
~~~
public function rules()
{
return [
// name,email,subject 和 body 特性是 `require`(必填)的
[['name', 'email', 'subject', 'body'], 'required'],
// email 特性必須是一個有效的 email 地址
['email', 'email'],
];
}
~~~
yii\base\Model::rules() 方法應返回一個由規則所組成的數組,每一個規則都呈現為以下這類格式的小數組:
~~~
[
// 必須項,用于指定那些模型特性需要通過此規則的驗證。
// 對于只有一個特性的情況,可以直接寫特性名,而不必用數組包裹。
['attribute1', 'attribute2', ...],
// 必填項,用于指定規則的類型。
// 它可以是類名,驗證器昵稱,或者是驗證方法的名稱。
'validator',
// 可選項,用于指定在場景(scenario)中,需要啟用該規則
// 若不提供,則代表該規則適用于所有場景
// 若你需要提供除了某些特定場景以外的所有其他場景,你也可以配置 "except" 選項
'on' => ['scenario1', 'scenario2', ...],
// 可選項,用于指定對該驗證器對象的其他配置選項
'property1' => 'value1', 'property2' => 'value2', ...
]
~~~
對于每個規則,你至少需要指定該規則適用于哪些特性,以及本規則的類型是什么。你可以指定以下的規則類型之一:
* 核心驗證器的昵稱,比如?`required`、`in`、`date`,等等。請參考[核心驗證器](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators)章節查看完整的核心驗證器列表。
* 模型類中的某個驗證方法的名稱,或者一個匿名方法。請參考[行內驗證器](http://www.yiichina.com/doc/guide/2.0/input-validation#inline-validators)小節了解更多。
* 驗證器類的名稱。請參考[獨立驗證器](http://www.yiichina.com/doc/guide/2.0/input-validation#standalone-validators)小節了解更多。
一個規則可用于驗證一個或多個模型特性,且一個特性可以被一個或多個規則所驗證。一個規則可以施用于特定[場景(scenario)](http://www.yiichina.com/doc/guide/2.0/structure-models#scenarios),只要指定?`on`?選項。如果你不指定?`on`?選項,那么該規則會適配于所有場景。
當調用?`validate()`?方法時,它將運行以下幾個具體的驗證步驟:
1. 檢查從聲明自 yii\base\Model::scenarios() 方法的場景中所挑選出的當前yii\base\Model::scenario的信息,從而確定出那些特性需要被驗證。這些特性被稱為激活特性。
2. 檢查從聲明自 yii\base\Model::rules() 方法的眾多規則中所挑選出的適用于當前yii\base\Model::scenario的規則,從而確定出需要驗證哪些規則。這些規則被稱為激活規則。
3. 用每個激活規則去驗證每個與之關聯的激活特性。
基于以上驗證步驟,有且僅有聲明在?`scenarios()`?方法里的激活特性,且它還必須與一或多個聲明自?`rules()`?里的激活規則相關聯才會被驗證。
### 自定義錯誤信息
大多數的驗證器都有默認的錯誤信息,當模型的某個特性驗證失敗的時候,該錯誤信息會被返回給模型。比如,用 yii\validators\RequiredValidator 驗證器的規則檢驗?`username`?特性失敗的話,會返還給模型 "Username cannot be blank." 信息。
你可以通過在聲明規則的時候同時指定?`message`?屬性,來定制某個規則的錯誤信息,比如這樣:
~~~
public function rules()
{
return [
['username', 'required', 'message' => 'Please choose a username.'],
];
}
~~~
一些驗證器還支持用于針對不同原因的驗證失敗返回更加準確的額外錯誤信息。比如,yii\validators\NumberValidator 驗證器就支持 yii\validators\NumberValidator::tooBig 和 yii\validators\NumberValidator::tooSmall 兩種錯誤消息用于分別返回輸入值是太大還是太小。 你也可以像配置驗證器的其他屬性一樣配置它們倆各自的錯誤信息。
### 驗證事件
當調用 yii\base\Model::validate() 方法的過程里,它同時會調用兩個特殊的方法,把它們重寫掉可以實現自定義驗證過程的目的:
* yii\base\Model::beforeValidate():在默認的實現中會觸發 yii\base\Model::EVENT_BEFORE_VALIDATE 事件。你可以重寫該方法或者響應此事件,來在驗證開始之前,先進行一些預處理的工作。(比如,標準化數據輸入)該方法應該返回一個布爾值,用于標明驗證是否通過。
* yii\base\Model::afterValidate():在默認的實現中會觸發 yii\base\Model::EVENT_AFTER_VALIDATE 事件。你可以重寫該方法或者響應此事件,來在驗證結束之后,再進行一些收尾的工作。
### 條件式驗證
若要只在某些條件滿足時,才驗證相關特性,比如:是否驗證某特性取決于另一特性的值,你可以通過 yii\validators\Validator::when 屬性來定義相關條件。舉例而言,
~~~
[
['state', 'required', 'when' => function($model) {
return $model->country == 'USA';
}],
]
~~~
yii\validators\Validator::when 屬性會讀入一個如下所示結構的 PHP callable 函數對象:
~~~
/**
* @param Model $model 要驗證的模型對象
* @param string $attribute 待測特性名
* @return boolean 返回是否啟用該規則
*/
function ($model, $attribute)
~~~
若你需要支持客戶端的條件驗證,你應該配置 yii\validators\Validator::whenClient 屬性,它會讀入一條包含有 JavaScript 函數的字符串。這個函數將被用于確定該客戶端驗證規則是否被啟用。比如,
~~~
[
['state', 'required', 'when' => function ($model) {
return $model->country == 'USA';
}, 'whenClient' => "function (attribute, value) {
return $('#country').value == 'USA';
}"],
]
~~~
### 數據預處理
用戶輸入經常需要進行數據過濾,或者叫預處理。比如你可能會需要先去掉?`username`?輸入的收尾空格。你可以通過使用驗證規則來實現此目的。
下面的例子展示了如何去掉輸入信息的首尾空格,并將空輸入返回為 null。具體方法為通過調用?[trim](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators#trim)?和?[default](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators.md#default)?核心驗證器:
~~~
[
[['username', 'email'], 'trim'],
[['username', 'email'], 'default'],
]
~~~
也還可以用更加通用的?[filter(濾鏡)](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators#filter)?核心驗證器來執行更加復雜的數據過濾。
如你所見,這些驗證規則并不真的對輸入數據進行任何驗證。而是,對輸入數據進行一些處理,然后把它們存回當前被驗證的模型特性。
### 處理空輸入
當輸入數據是通過 HTML 表單,你經常會需要給空的輸入項賦默認值。你可以通過調整?[default](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators#default)?驗證器來實現這一點。舉例來說,
~~~
[
// 若 "username" 和 "email" 為空,則設為 null
[['username', 'email'], 'default'],
// 若 "level" 為空,則設其為 1
['level', 'default', 'value' => 1],
]
~~~
默認情況下,當輸入項為空字符串,空數組,或 null 時,會被視為“空值”。你也可以通過配置 yii\validators\Validator::isEmpty 屬性來自定義空值的判定規則。比如,
~~~
[
['agree', 'required', 'isEmpty' => function ($value) {
return empty($value);
}],
]
~~~
> 注意:對于絕大多數驗證器而言,若其 yii\base\Validator::skipOnEmpty 屬性為默認值 true,則它們不會對空值進行任何處理。也就是當他們的關聯特性接收到空值時,相關驗證會被直接略過。在?[核心驗證器](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators)?之中,只有?`captcha`(驗證碼),`default`(默認值),`filter`(濾鏡),`required`(必填),以及?`trim`(去首尾空格),這幾個驗證器會處理空輸入。
## 臨時驗證
有時,你需要對某些沒有綁定任何模型類的值進行?**臨時驗證**。
若你只需要進行一種類型的驗證 (e.g. 驗證郵箱地址),你可以調用所需驗證器的 yii\validators\Validator::validate() 方法。像這樣:
~~~
$email = 'test@example.com';
$validator = new yii\validators\EmailValidator();
if ($validator->validate($email, $error)) {
echo '有效的 Email 地址。';
} else {
echo $error;
}
~~~
> 注意:不是所有的驗證器都支持這種形式的驗證。比如?[unique(唯一性)](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators#unique)核心驗證器就就是一個例子,它的設計初衷就是只作用于模型類內部的。
若你需要針對一系列值執行多項驗證,你可以使用 yii\base\DynamicModel 。它支持即時添加特性和驗證規則的定義。它的使用規則是這樣的:
~~~
public function actionSearch($name, $email)
{
$model = DynamicModel::validateData(compact('name', 'email'), [
[['name', 'email'], 'string', 'max' => 128],
['email', 'email'],
]);
if ($model->hasErrors()) {
// 驗證失敗
} else {
// 驗證成功
}
}
~~~
yii\base\DynamicModel::validateData() 方法會創建一個?`DynamicModel`?的實例對象,并通過給定數據定義模型特性(以?`name`?和`email`?為例),之后用給定規則調用 yii\base\Model::validate() 方法。
除此之外呢,你也可以用如下的更加“傳統”的語法來執行臨時數據驗證:
~~~
public function actionSearch($name, $email)
{
$model = new DynamicModel(compact('name', 'email'));
$model->addRule(['name', 'email'], 'string', ['max' => 128])
->addRule('email', 'email')
->validate();
if ($model->hasErrors()) {
// 驗證失敗
} else {
// 驗證成功
}
}
~~~
驗證之后你可以通過調用 yii\base\DynamicModel::hasErrors() 方法來檢查驗證通過與否,并通過 yii\base\DynamicModel::errors 屬性獲得驗證的錯誤信息,過程與普通模型類一致。你也可以訪問模型對象內定義的動態特性,就像:?`$model->name`?和?`$model->email`。
## 創建驗證器(Validators)
除了使用 Yii 的發布版里所包含的[核心驗證器](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators)之外,你也可以創建你自己的驗證器。自定義的驗證器可以是**行內驗證器**,也可以是**獨立驗證器**。
### 行內驗證器(Inline Validators)
行內驗證器是一種以模型方法或匿名函數的形式定義的驗證器。這些方法/函數的結構如下:
~~~
/**
* @param string $attribute 當前被驗證的特性
* @param array $params 以名-值對形式提供的額外參數
*/
function ($attribute, $params)
~~~
若某特性的驗證失敗了,該方法/函數應該調用 yii\base\Model::addError() 保存錯誤信息到模型內。這樣這些錯誤就能在之后的操作中,被讀取并展現給終端用戶。
下面是一些例子:
~~~
use yii\base\Model;
class MyForm extends Model
{
public $country;
public $token;
public function rules()
{
return [
// 以模型方法 validateCountry() 形式定義的行內驗證器
['country', 'validateCountry'],
// 以匿名函數形式定義的行內驗證器
['token', function ($attribute, $params) {
if (!ctype_alnum($this->$attribute)) {
$this->addError($attribute, 'token 本身必須包含字母或數字。');
}
}],
];
}
public function validateCountry($attribute, $params)
{
if (!in_array($this->$attribute, ['USA', 'Web'])) {
$this->addError($attribute, 'The country must be either "USA" or "Web".');
}
}
}
~~~
> 注意:缺省狀態下,行內驗證器不會在關聯特性的輸入值為空或該特性已經在其他驗證中失敗的情況下起效。若你想要確保該驗證器始終啟用的話,你可以在定義規則時,酌情將 yii\validators\Validator::skipOnEmpty 以及 yii\validators\Validator::skipOnError 屬性設為 false,比如,?`````php [
>
> ~~~
> ['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false],
> ~~~
>
> ]?`````
### 獨立驗證器(Standalone Validators)
獨立驗證器是繼承自 yii\validators\Validator 或其子類的類。你可以通過重寫 yii\validators\Validator::validateAttribute() 來實現它的驗證規則。若特性驗證失敗,可以調用 yii\base\Model::addError() 以保存錯誤信息到模型內,操作與?[inline validators](http://www.yiichina.com/doc/guide/2.0/input-validation#inline-validators)?所需操作完全一樣。比如,
~~~
namespace app\components;
use yii\validators\Validator;
class CountryValidator extends Validator
{
public function validateAttribute($model, $attribute)
{
if (!in_array($model->$attribute, ['USA', 'Web'])) {
$this->addError($attribute, 'The country must be either "USA" or "Web".');
}
}
}
~~~
若你想要驗證器支持不使用 model 的數據驗證,你還應該重寫 yii\validators\Validator::validate() 方法。你也可以通過重寫 yii\validators\Validator::validateValue() 方法替代?`validateAttribute()`?和?`validate()`,因為默認狀態下,后兩者的實現使用過調用?`validateValue()`實現的。
## 客戶端驗證器(Client-Side Validation)
當終端用戶通過 HTML 表單提供相關輸入信息時,我們可能會需要用到基于 JavaScript 的客戶端驗證。因為,它可以讓用戶更快速的得到錯誤信息,也因此可以提供更好的用戶體驗。你可以使用或自己實現除服務器端驗證之外,**還能額外**客戶端驗證功能的驗證器。
> 補充:盡管客戶端驗證為加分項,但它不是必須項。它存在的主要意義在于給用戶提供更好的客戶體驗。正如“永遠不要相信來自終端用戶的輸入信息”,也同樣永遠不要相信客戶端驗證。基于這個理由,你應該始終如前文所描述的那樣,通過調用 yii\base\Model::validate() 方法執行服務器端驗證。
### 使用客戶端驗證
許多[核心驗證器](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators)都支持開箱即用的客戶端驗證。你只需要用 yii\widgets\ActiveForm 的方式構建 HTML 表單即可。比如,下面的`LoginForm`(登錄表單)聲明了兩個規則:其一為?[required](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators#required)?核心驗證器,它同時支持客戶端與服務器端的驗證;另一個則采用`validatePassword`?行內驗證器,它只支持服務器端。
~~~
namespace app\models;
use yii\base\Model;
use app\models\User;
class LoginForm extends Model
{
public $username;
public $password;
public function rules()
{
return [
// username 和 password 都是必填項
[['username', 'password'], 'required'],
// 用 validatePassword() 驗證 password
['password', 'validatePassword'],
];
}
public function validatePassword()
{
$user = User::findByUsername($this->username);
if (!$user || !$user->validatePassword($this->password)) {
$this->addError('password', 'Incorrect username or password.');
}
}
}
~~~
使用如下代碼構建的 HTML 表單包含兩個輸入框?`username`?以及?`password`。如果你在沒有輸入任何東西之前提交表單,就會在沒有任何與服務器端的通訊的情況下,立刻收到一個要求你填寫空白項的錯誤信息。
~~~
<?php $form = yii\widgets\ActiveForm::begin(); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= Html::submitButton('Login') ?>
<?php yii\widgets\ActiveForm::end(); ?>
~~~
幕后的運作過程是這樣的:yii\widgets\ActiveForm 會讀取聲明在模型類中的驗證規則,并生成那些支持支持客戶端驗證的驗證器所需的 JavaScript 代碼。當用戶修改輸入框的值,或者提交表單時,就會觸發相應的客戶端驗證 JS 代碼。
若你需要完全關閉客戶端驗證,你只需配置 yii\widgets\ActiveForm::enableClientValidation 屬性為 false。你同樣可以關閉各個輸入框各自的客戶端驗證,只要把它們的 yii\widgets\ActiveField::enableClientValidation 屬性設為 false。
### 自己實現客戶端驗證
要穿件一個支持客戶端驗證的驗證器,你需要實現 yii\validators\Validator::clientValidateAttribute() 方法,用于返回一段用于運行客戶端驗證的 JavaScript 代碼。在這段 JavaScript 代碼中,你可以使用以下預定義的變量:
* `attribute`:正在被驗證的模型特性的名稱。
* `value`:進行驗證的值。
* `messages`:一個用于暫存模型特性的報錯信息的數組。
在下面的例子里,我們會創建一個?`StatusValidator`,它會通過比對現有的狀態數據,驗證輸入值是否為一個有效的狀態。該驗證器同時支持客戶端以及服務器端驗證。
~~~
namespace app\components;
use yii\validators\Validator;
use app\models\Status;
class StatusValidator extends Validator
{
public function init()
{
parent::init();
$this->message = '無效的狀態輸入。';
}
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (!Status::find()->where(['id' => $value])->exists()) {
$model->addError($attribute, $this->message);
}
}
public function clientValidateAttribute($model, $attribute, $view)
{
$statuses = json_encode(Status::find()->select('id')->asArray()->column());
$message = json_encode($this->message);
return <<<JS
if (!$.inArray(value, $statuses)) {
messages.push($message);
}
JS;
}
}
~~~
> 技巧:上述代碼主要是演示了如何支持客戶端驗證。在具體實踐中,你可以使用?[in](http://www.yiichina.com/doc/guide/2.0/tutorial-core-validators#in)?核心驗證器來達到同樣的目的。比如這樣的驗證規則:?`````php [
>
> ~~~
> ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()],
> ~~~
>
> ]?`````
- 介紹(Introduction)
- 關于 Yii(About Yii)
- 從 Yii 1.1 升級(Upgrading from Version 1.1)
- 入門(Getting Started)
- 安裝 Yii(Installing Yii)
- 運行應用(Running Applications)
- 第一次問候(Saying Hello)
- 使用 Forms(Working with Forms)
- 玩轉 Databases(Working with Databases)
- 用 Gii 生成代碼(Generating Code with Gii)
- 更上一層樓(Looking Ahead)
- 應用結構(Application Structure)
- 結構概述(Overview)
- 入口腳本(Entry Scripts)
- 應用(Applications)
- 應用組件(Application Components)
- 控制器(Controllers)
- 模型(Models)
- 視圖(Views)
- 模塊(Modules)
- 過濾器(Filters)
- 小部件(Widgets)
- 前端資源(Assets)
- 擴展(Extensions)
- 請求處理(Handling Requests)
- 運行概述(Overview)
- 引導(Bootstrapping)
- 路由引導與創建 URL(Routing and URL Creation)
- 請求(Requests)
- 響應(Responses)
- Sessions and Cookies
- 錯誤處理(Handling Errors)
- 日志(Logging)
- 關鍵概念(Key Concepts)
- 組件(Components)
- 屬性(Properties)
- 事件(Events)
- 行為(Behaviors)
- 配置(Configurations)
- 別名(Aliases)
- 類自動加載(Class Autoloading)
- 服務定位器(Service Locator)
- 依賴注入容器(Dependency Injection Container)
- 配合數據庫工作(Working with Databases)
- 數據庫訪問(Data Access Objects): 數據庫連接、基本查詢、事務和模式操作
- 查詢生成器(Query Builder): 使用簡單抽象層查詢數據庫
- 活動記錄(Active Record): 活動記錄對象關系映射(ORM),檢索和操作記錄、定義關聯關系
- 數據庫遷移(Migrations): 在團體開發中對你的數據庫使用版本控制
- Sphinx
- Redis
- MongoDB
- ElasticSearch
- 接收用戶數據(Getting Data from Users)
- 創建表單(Creating Forms)
- 輸入驗證(Validating Input)
- 文件上傳(Uploading Files)
- 收集列表輸入(Collecting Tabular Input)
- 多模型同時輸入(Getting Data for Multiple Models)
- 顯示數據(Displaying Data)
- 格式化輸出數據(Data Formatting)
- 分頁(Pagination)
- 排序(Sorting)
- 數據提供器(Data Providers)
- 數據小部件(Data Widgets)
- 操作客戶端腳本(Working with Client Scripts)
- 主題(Theming)
- 安全(Security)
- 認證(Authentication)
- 授權(Authorization)
- 處理密碼(Working with Passwords)
- 客戶端認證(Auth Clients)
- 安全領域的最佳實踐(Best Practices)
- 緩存(Caching)
- 概述(Overview)
- 數據緩存(Data Caching)
- 片段緩存(Fragment Caching)
- 分頁緩存(Page Caching)
- HTTP 緩存(HTTP Caching)
- RESTful Web 服務
- 快速入門(Quick Start)
- 資源(Resources)
- 控制器(Controllers)
- 路由(Routing)
- 格式化響應(Response Formatting)
- 授權驗證(Authentication)
- 速率限制(Rate Limiting)
- 版本化(Versioning)
- 錯誤處理(Error Handling)
- 開發工具(Development Tools)
- 調試工具欄和調試器(Debug Toolbar and Debugger)
- 使用 Gii 生成代碼(Generating Code using Gii)
- TBD 生成 API 文檔(Generating API Documentation)
- 測試(Testing)
- 概述(Overview)
- 搭建測試環境(Testing environment setup)
- 單元測試(Unit Tests)
- 功能測試(Functional Tests)
- 驗收測試(Acceptance Tests)
- 測試夾具(Fixtures)
- 高級專題(Special Topics)
- 高級應用模版(Advanced Project Template)
- 從頭構建自定義模版(Building Application from Scratch)
- 控制臺命令(Console Commands)
- 核心驗證器(Core Validators)
- 國際化(Internationalization)
- 收發郵件(Mailing)
- 性能優化(Performance Tuning)
- 共享主機環境(Shared Hosting Environment)
- 模板引擎(Template Engines)
- 集成第三方代碼(Working with Third-Party Code)
- 小部件(Widgets)
- Bootstrap 小部件(Bootstrap Widgets)
- jQuery UI 小部件(jQuery UI Widgets)
- 助手類(Helpers)
- 助手一覽(Overview)
- Array 助手(ArrayHelper)
- Html 助手(Html)
- Url 助手(Url)