首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Yii 框架表單模型與驗證機制深度解析:CFormModel、rules()、驗證器創建流程

     編輯:PHP 相關知識

Yii 1.1 表單模型與驗證機制深度解析

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();        // 是否有任何錯誤

五、客戶端驗證 (AJAX/JS) 整合

  • 內建驗證器 (required, email, match 等) 皆實現 clientValidateAttribute() 產生 JS 代碼。
  • 視圖中啟用:beginWidget('CActiveForm', ['enableClientValidation'=>true]); ?>
  • 自定義驗證器需在規則指定 'clientValidate'=>'js:function(value, messages, attribute) { ... }'。

六、最佳實踐總結

  1. 表單模型專職:僅處理輸入驗證、資料轉換、錯誤收集,**不含資料庫邏輯** (那是 ActiveRecord)。
  2. 規則集中宣告:rules() 返回陣列,每項 [attributes, validator, ...params]。
  3. 自定義驗證用內聯方法:['attr', 'methodName'],方法簽名 methodName($attribute, $params)。
  4. 場景控制:['attr', 'required', 'on'=>'register,update'],配合 $model->scenario = 'register'。
  5. 安全屬性原則:預設只賦值 rules 出現的屬性,需額外賦值時明確 ['attr', 'safe']。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved