复杂逻辑校验须拆分为内联验证器或独立验证类:内联适用于单次、轻量、字段强依赖场景,需用匿名函数或[$this,'m']声明且严格遵循方法签名;独立类适用于复用、跨模型、含查询/正则等重逻辑,须以Validator结尾、继承yii\validators\Validator并重写validateAttribute();when与skipOnEmpty是顺序执行而非“且”关系,when为false直接跳过整条规则;rules未生效主因包括上下文未触发、字段不在safe列表、自定义方法非public或语法错误。

复杂逻辑校验不能靠内置验证器硬凑,必须拆成内联验证器或独立验证类——否则规则耦合、复用困难、调试黑洞。
什么时候该写内联验证器
适合只在当前模型用一次、逻辑不重、字段间强依赖的场景,比如“当 status 为 1 时 reason 必填”,或“password 和 password_repeat 必须一致”。
- 必须用匿名函数或
[$this, 'methodName']写进rules(),不能只写字符串'methodName',否则静默失效 - 方法签名固定为
function ($model, $attribute, $params),漏掉$params或加额外参数,运行时不报错但直接跳过 - 验证失败必须调用
$model->addError($attribute, $message),return false / throw Exception 都无效 - 默认不跳过空值:即使
$model->reason === '',验证器仍会执行——需手动加if (empty($model->$attribute)) { return; }
示例:
public function rules()
{
return [
[['reason'], function ($model, $attribute, $params) {
if ($model->status == 1 && empty($model->$attribute)) {
$model->addError($attribute, '状态为1时,原因必填');
}
}],
];
}
什么时候该写独立验证类
当逻辑涉及正则、数据库查询、多字段协同、跨模型校验(如检查邮箱是否在 UserProfile 表存在),或需要被多个模型复用时,必须抽成类。
- 类名必须以
Validator结尾(如IDCardValidator),否则 Yii 自动加载失败 - 必须继承
yii\validators\Validator,并重写validateAttribute();写validate()没用 - 参数通过 public 属性声明(如
public $strict = true;),然后在rules()中传入,别试图从$params里解析配置 - 若要支持客户端验证,必须实现
clientValidateAttribute()并返回合法 JS 字符串,否则表单失去焦点时不触发
示例(简化版手机号校验):
class MobileValidator extends Validator
{
public $message = '手机号格式不正确';
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (!preg_match('/^1[3-9]\d{9}$/', $value)) {
$this->addError($model, $attribute, $this->message);
}
}
}
when 和 skipOnEmpty 的真实行为
二者共存时,when 先判断:只有 when 返回 true,才进入 skipOnEmpty 判断。很多人以为它们是“且”关系,其实不是。
-
when是开关:返回 false 直接跳过整条规则,连空值判断都不走 -
skipOnEmpty默认为 true,意味着只要属性值是null、''或0(数字零),就跳过验证——注意:0 是合法数值时这会导致误判 - 想让规则对空值也生效,必须显式设
'skipOnEmpty' => false,尤其在自定义方法中依赖字段原始值时 - 若同时设了
'on' => ['update']和when,场景匹配后才执行when回调
常见错误写法:
[['mobile'], 'app\validators\MobileValidator', 'when' => function ($model) {
return $model->type === 'user'; // ✅ 正确:闭包返回布尔值
}],
错误写法:'when' => '$model->type === "user"'(字符串不会被求值)。
为什么 rules() 看似写了却没生效
最常被忽略的是验证上下文未触发——validate() 调用前,字段可能已被过滤或未加载。
- 确保
$model->load(Yii::$app->request->post())成功,且字段名与 POST 键完全一致(含大小写) - 打印
var_dump($model->getErrors()),看是否有其他规则先失败导致skipOnError生效(默认为 true) - 检查字段是否在
safe列表里:没出现在任何rules()中的字段,默认被 mass assignment 过滤掉,根本进不到验证流程 - 自定义方法必须是
public,且不能有 PHP 语法错误(如 use 语句缺失、命名空间错),否则整个rules()数组解析失败,静默回退到空数组
复杂校验真正难的不是写代码,而是厘清“谁在什么时候、以什么顺序、对什么值做判断”。一旦验证链某个环节断开,错误就不会出现,而不是报错——这种沉默,比报错更难排查。


















