ThinkPHP 2.x/3.x 的自動驗證(_validate)是模型層資料完整性的第一道防線。但實務上常遇到「驗證不生效」、「getError() 回傳空值」、「Token 驗證衝突」等問題。本文系統整理失效常見原因、除錯步驟與修正方案。
| 現象 | 常見原因 | 快速驗證 |
|---|---|---|
create() 總回傳 true,未驗證 | 1. 模型類別未繼承 Model2. _validate 陣列語法錯誤3. 欄位名稱與表單 name 不符 | var_dump($model->_validate) 檢查定義 |
create() 回傳 false 但 getError() 為空 | 1. 驗證規則未匹配(條件參數錯誤) 2. create() 參數 '' 導致重置資料3. 自動完成 _auto 衝突 | 加 var_dump($model->getError()) 即時看 |
| 重新整理出現「令牌表單錯誤」 | 1. 表單缺少 {:token()} 隱藏欄位2. Session 未啟動或儲存失敗 3. 多標籤頁同時操作 Token 失效 | 檢查 HTML 原始碼是否有 <input name="__hash__"> |
| 驗證規則部分生效 | 1. 規則順序:require 應在最前2. 正則未加分隔符 3. 回調函數回傳非布林 | 逐條註解測試 |
<?php
namespace Home\Model;
use Think\Model;
class LoginModel extends Model {
protected $tableName = 'admin'; // 實際資料表
// 自動驗證規則
protected $_validate = [
// [欄位, 規則, 錯誤訊息, 條件, 類型, 時間]
['username', 'require', '帳號不得為空', self::MUST_VALIDATE, 'regex', self::MODEL_BOTH],
['username', '/^[a-zA-Z0-9_]{3,20}$/', '帳號格式錯誤', self::VALUE_VALIDATE, 'regex', self::MODEL_BOTH],
['password', 'require', '密碼不得為空', self::MUST_VALIDATE, 'regex', self::MODEL_BOTH],
['password', '/^.{6,32}$/', '密碼長度 6-32 字元', self::VALUE_VALIDATE, 'regex', self::MODEL_BOTH],
// 回調函數驗證(需在模型內定義 public/protected 方法)
['email', 'checkEmail', 'Email 格式錯誤', self::VALUE_VALIDATE, 'callback', self::MODEL_BOTH],
];
// 自動完成(選用)
protected $_auto = [
['status', 1, self::MODEL_INSERT],
['create_time', 'time', self::MODEL_INSERT, 'function'],
['update_time', 'time', self::MODEL_BOTH, 'function'],
];
// 回調驗證方法
protected function checkEmail($value) {
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
}
}
$_POST['field'] 鍵名。require、email、url、currency、number、zip、ip、regex(正則)、function(回調)、confirm(比對另一欄位)。self::MUST_VALIDATE (1):必須驗證(欄位存在即驗證,含空值)。self::VALUE_VALIDATE (2):值不為空時驗證(預設)。self::EXISTS_VALIDATE (0):欄位存在於資料陣列即驗證。regex、function、callback、confirm、unique(唯一值)。self::MODEL_INSERT (1)、self::MODEL_UPDATE (2)、self::MODEL_BOTH (3)。public function login() {
if (IS_POST) {
$model = D('Login'); // 實例化模型
// 1. create() 自動驗證 + 資料物件建立
// 參數為空字串表示使用 $_POST,也可傳入陣列
if (!$model->create()) {
// 驗證失敗
$this->error($model->getError());
return;
}
// 2. 業務邏輯(密碼加密、查詢比對)
$map = [
'username' => $model->username,
'password' => md5($model->password . C('PWD_SALT')), // 加鹽
];
$user = $model->where($map)->find();
if ($user) {
session('user_id', $user['id']);
session('username', $user['username']);
$this->success('登入成功', U('Index/index'));
} else {
$this->error('帳號或密碼錯誤');
}
} else {
$this->display(); // 輸出登入模板
}
}
<!-- 模板登入表單 -->
<form action="__URL__" method="post">
<input type="text" name="username" placeholder="帳號" required>
<input type="password" name="password" placeholder="密碼" required>
<input type="email" name="email" placeholder="Email">
<!-- 必須:Token 隱藏欄位 -->
<input type="hidden" name="__hash__" value="{:token()}">
<button type="submit">登入</button>
</form>
C('TOKEN_ON', true)(預設開啟)。create() 回傳 false,getError() 回傳 令牌表單錯誤。X-CSRF-Token: {:token()}。<?php
// 1. 檢查驗證規則是否正確載入
$model = D('Login');
var_dump($model->_validate); // 應輸出陣列定義
// 2. 檢查 create() 內部資料
$model->create();
var_dump($model->getError()); // 立即查看錯誤
var_dump($model->getDbFields()); // 模型對應欄位
// 3. 開啟 SQL 日誌
C('DB_SQL_LOG', true);
// 執行後查看 Runtime/Logs/.../sql.log
// 4. 手動觸發驗證(繞過 create)
$validate = $model->getValidate();
$result = $model->_validate($data, $validate); // 內部方法,慎用
| 錯誤程式碼/設定 | 問題 | 修正 |
|---|---|---|
protected $_validate = array('username', 'require', '...'); | 二維陣列缺少外層 array() | 每條規則用 array(...) 包裹 |
'password', '/^\d+$/', '...', 1, 'regex' | 正則未加分隔符 /.../ | 補上 /^\\d+$/ |
'repass', 'confirm', '...', 1, 'confirm', 3, 'password' | confirm 第 6 參數應為比對欄位名 | 'repass', 'confirm', '...', 1, 'confirm', 3, 'password'(正確) |
模型類別名 LoginModel,控制器 D('Login') | 命名對應:D('Login') 對應 LoginModel | 確認命名空間與檔案位置 |
表單 name="user",驗證欄位 username | 欄位名不匹配 | 統一命名 |
ThinkPHP 5.0+ 棄用模型內建 _validate,改用獨立 驗證器類別:
// app/validate/User.php
namespace app\validate;
use think\Validate;
class User extends Validate {
protected $rule = [
'username' => 'require|alphaNum|length:3,20',
'password' => 'require|length:6,32',
'email' => 'email',
];
protected $message = [
'username.require' => '帳號不得為空',
'username.alphaNum' => '帳號僅限英數底線',
];
}
// 控制器
$validate = new \app\validate\User();
if (!$validate->check($data)) {
$this->error($validate->getError());
}
scene('login'))、獨立於模型。總結:ThinkPHP 自動驗證失效 90% 來自「規則定義語法錯誤」、「欄位名不匹配」、「Token 缺失」。遵循標準定義範本、善用除錯輸出、正確整合 Token,即可穩定運作。長期規劃應遷移至獨立驗證器架構。