Yii 表單模型 (CFormModel) 繼承自 CModel,專為**非持久化資料** (如登入表單、搜尋過濾、聯絡表單) 設計。核心在於 **rules() 宣告驗證規則 → CValidator 驗證器工廠創建 → validate() 執行驗證**。本文剖析完整流程與源碼實現。
class LoginForm extends CFormModel {
public $username;
public $password;
public $rememberMe = false;
// 宣告驗證規則
public function rules() {
return [
['username, password', 'required'], // 必填
['rememberMe', 'boolean'], // 布林型別
['password', 'authenticate'], // 自定義驗證方法 (本模型內)
// ['email', 'email'], // 內建 email 驗證器
// ['age', 'numerical', 'integerOnly'=>true], // 內建 numerical 驗證器
// ['file', 'file', 'types'=>'jpg,png'], // 內建 file 驗證器
];
}
// 自定義驗證邏輯
public function authenticate($attribute, $params) {
if (!$this->hasErrors()) {
$identity = new UserIdentity($this->username, $this->password);
if (!$identity->authenticate()) {
$this->addError('password', '帳號或密碼錯誤');
}
}
}
}
public function actionLogin() {
$model = new LoginForm;
// 1. 大量賦值 (Massive Assignment) — 僅賦值「安全屬性」
if (isset($_POST['LoginForm'])) {
$model->attributes = $_POST['LoginForm']; // 內部調用 setAttributes()
}
// 2. 執行驗證
if ($model->validate()) {
// 驗證通過,登入邏輯
Yii::app()->user->login($identity);
$this->redirect(Yii::app()->user->returnUrl);
}
// 3. 渲染表單 (含錯誤訊息)
$this->render('login', ['model' => $model]);
}
setAttributes() 安全屬性機制rules() 中出現過的屬性** (安全屬性)。rules() 額外宣告 ['attr', 'safe'] 標記為安全 (無驗證但可賦值)。$model->setAttributes($_POST['LoginForm'], false) (第二參數 false 關閉安全檢查,**慎用**)。CValidator::createValidator())public static function createValidator($name, $object, $attributes, $params = []) {
// 1. 解析屬性字串為陣列
if (is_string($attributes)) {
$attributes = preg_split('/[\s,]+/', $attributes, -1, PREG_SPLIT_NO_EMPTY);
}
// 2. 解析場景 on
$on = [];
if (isset($params['on'])) {
$on = is_array($params['on']) ? $params['on'] : preg_split('/[\s,]+/', $params['on'], -1, PREG_SPLIT_NO_EMPTY);
}
// 3. 判斷是「內聯驗證器」 (模型內方法) 還是「獨立驗證器類」
if (method_exists($object, $name)) {
// 3a. 內聯驗證器
$validator = new CInlineValidator;
$validator->attributes = $attributes;
$validator->method = $name; // 方法名
$validator->params = $params; // 額外參數
if (isset($params['clientValidate'])) {
$validator->clientValidate = $params['clientValidate']; // JS 驗證回調
unset($params['clientValidate']);
}
if (isset($params['skipOnError'])) $validator->skipOnError = $params['skipOnError'];
} else {
// 3b. 獨立驗證器類
$params['attributes'] = $attributes;
if (isset(self::$builtInValidators[$name])) {
$className = Yii::import(self::$builtInValidators[$name], true); // 內建別名映射
} else {
$className = Yii::import($name, true); // 完整類路徑或別名
}
$validator = new $className;
foreach ($params as $k => $v) $validator->$k = $v; // 批量設定屬性
}
// 4. 場景綁定
$validator->on = empty($on) ? [] : array_combine($on, $on);
return $validator;
}
CValidator::$builtInValidators)static $builtInValidators = [
'required' => 'system.validators.CRequiredValidator',
'filter' => 'system.validators.CFilterValidator',
'match' => 'system.validators.CRegularExpressionValidator',
'email' => 'system.validators.CEmailValidator',
'url' => 'system.validators.CUrlValidator',
'type' => 'system.validators.CTypeValidator',
'length' => 'system.validators.CStringValidator',
'range' => 'system.validators.CRangeValidator',
'compare' => 'system.validators.CCompareValidator',
'exist' => 'system.validators.CExistValidator',
'unique' => 'system.validators.CUniqueValidator',
'file' => 'system.validators.CFileValidator',
'boolean' => 'system.validators.CBooleanValidator',
'number' => 'system.validators.CNumberValidator',
'captcha' => 'system.validators.CCaptchaValidator',
];
// CModel::validate()
public function validate($attributes = null, $clearErrors = true) {
if ($clearErrors) $this->clearErrors();
$validators = $this->getValidators(); // 延遲創建、快取驗證器集合
foreach ($validators as $validator) {
if ($validator->validate($this, $attributes)) continue;
// 驗證失敗:錯誤已由 validator->addError() 寫入 $this->_errors
if ($validator->skipOnError) break; // 可選:首錯即停
}
return !$this->hasErrors();
}
// 取得錯誤
$model->getErrors(); // 所有屬性錯誤陣列 ['attr' => ['msg1', 'msg2']]
$model->getError('attr'); // 指定屬性第一條錯誤
$model->hasErrors(); // 是否有任何錯誤
clientValidateAttribute() 產生 JS 代碼。beginWidget('CActiveForm', ['enableClientValidation'=>true]); ?>'clientValidate'=>'js:function(value, messages, attribute) { ... }'。rules() 返回陣列,每項 [attributes, validator, ...params]。['attr', 'methodName'],方法簽名 methodName($attribute, $params)。['attr', 'required', 'on'=>'register,update'],配合 $model->scenario = 'register'。['attr', 'safe']。