模型层验证分三层:模型自带规则、控制器显式调用、独立验证器类,三者互不叠加,最后一次生效;字段规则支持管道符但短路执行,$message键名须为“字段名.规则名”格式,空值处理需注意exists_validate机制。

模型层验证不是“加个规则就完事”,它分三层:模型自带规则、控制器显式调用、独立验证器类——三者互不叠加,最后一次生效的才起作用。
模型类里写 $rule 和 $message 是最常用方式
直接在模型中定义 protected $rule 和 protected $message,save() 时自动触发,不用每次手动调用 validate():
- 字段规则字符串支持管道符分隔,比如
'name' => 'require|max:20',但注意require失败后max不会再执行(短路) -
$message键名必须是"字段名.规则名"格式,例如'name.require' => '姓名不能为空',漏掉点或拼错就回退到默认提示 - 如果字段名含下划线或驼峰(如
user_name),$message中也得保持一致,不能写成username.require - 空值处理要小心:
require默认用Model::EXISTS_VALIDATE(字段存在即校验),若表单没传该字段,它根本不会进验证流程
控制器里用 $model->validate($rules) 会清空模型原有规则
这不是“补充规则”,而是彻底替换。常见错误是想“加一条 unique 验证”,结果把模型里已配好的 email、mobile 全干掉了:
- 传入的
$rules必须是完整数组,例如['name' => 'require', 'email' => 'email|unique:user,email'],缺一个字段就不校验 - 错误提示必须同步传第二个参数,否则用系统默认,比如
['name.require' => '名称必填'] - 场景验证(如
validate('User.edit'))本质是加载对应验证器类,和当前模型类的$rule完全无关 - 别在控制器里反复调
validate()—— 第二次调用会覆盖第一次,规则只保留最后一次的
联合唯一验证(unique)最容易踩坑的四个点
unique 看似简单,但字段顺序、表前缀、NULL 值、更新排除 ID 这四点一错,验证就形同虚设:
- 联合唯一必须写成
['unique' => ['user', 'username,email']],其中username,email的顺序必须和数据库联合索引字段顺序完全一致,否则 MySQL 不走索引 - 如果数据表用了前缀(如
tp_user),unique第一个参数就得写'tp_user',写'user'就查不到数据 - MySQL 对
UNIQUE索引中NULL值是宽松处理的,但 ThinkPHP 的unique规则会把null当普通值查,导致本该允许的记录被拦住 - 编辑时必须显式传主键,比如
['id' => 123, 'username' => 'a', 'email' => 'b@x.com'],否则验证器会把当前记录自己也算作重复;主键名不是id就得同步改规则里的字段列表
动态判断必填(如调 API 决定是否校验 price)不能塞进模型验证规则
模型验证是同步执行的,没法等 HTTP 请求返回。硬塞进去只会让请求卡死或超时,还破坏事务一致性:
- 正确做法是在
beforeWrite钩子里发请求,用think\Http或guzzlehttp/guzzle,设置timeout => 3 - 响应回来后手动判断,如果该字段应必填但为空,就抛
ValidateException,save()会自动中断 - 必须加缓存(如
Cache::remember())和降级开关(如配置'field_required_fallback' => 'loose'),否则第三方服务一挂,你整个写入链就瘫了 - 别信“运行时改
$rule数组”这种方案——规则在模型实例化时就编译完了,后面改只是改了个寂寞
真正难的不是写对那几行 $rule,而是搞清规则在哪一层生效、谁覆盖谁、以及当验证逻辑需要 IO 或外部依赖时,必须跳出声明式框架,回到命令式控制流里手动接管。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











