Yii2中实现多场景参数校验的核心是结合scenario与on/when条件控制:通过scenarios()定义各场景字段集,rules()中用on指定场景、when实现动态条件校验,并可复用自定义验证器。

Yii2 中实现不同场景下的参数分开校验,核心是结合 场景(scenario) 与 验证规则条件控制,而不是写多个模型或硬编码判断。关键在于让同一模型在不同业务入口下启用不同的字段集合和校验逻辑。
用 scenario 区分验证范围
每个场景对应一组“活动属性”和专属验证规则。比如注册、编辑、重置密码三个操作,字段需求和校验强度都不同:
- 注册需校验
username、email、password、verifyCode,且email必须唯一 - 编辑时
email可选,但若填了就要格式合法;password允许留空 - 重置密码只要求
email存在,不校验其他字段
先在模型中定义场景映射:
public function scenarios()
{
return [
'signup' => ['username', 'email', 'password', 'verifyCode'],
'update' => ['email', 'password', 'avatar'],
'reset' => ['email'],
];
}
再在 rules() 中按场景绑定规则:
public function rules()
{
return [
[['username', 'password'], 'required', 'on' => 'signup'],
['email', 'required', 'on' => ['signup', 'reset']],
['email', 'email', 'on' => ['signup', 'update', 'reset']],
['email', 'exist', 'targetClass' => User::class, 'on' => 'reset'],
['password', 'string', 'min' => 8, 'on' => 'signup'],
['password', 'filter', 'filter' => 'trim', 'on' => 'update'],
];
}
使用时指定场景即可触发对应规则:
$model = new User(['scenario' => 'reset']); $model->load(Yii::$app->request->post()); $model->validate(); // 只校验 email,且只走 'on' => 'reset' 的规则
用 on + when 精确控制某条规则的生效时机
场景只能控制“整组规则是否参与”,而 when 能让单条规则根据运行时数据动态开关。适合“字段间联动校验”:
- 当
status为 1 时,reason必填;否则不校验 - 当
type是 'mobile' 时,contact必须是手机号格式;否则跳过
写法示例:
['reason', 'required', 'on' => 'update', 'when' => function ($model) {
return $model->status == 1;
}]
['contact', 'phone', 'on' => 'update', 'when' => function ($model) {
return $model->type === 'mobile';
}]
when 回调返回 true 才执行该条规则;它比 on 更细粒度,且优先级更高——即使不在当前场景中,when 为真也会触发校验。
用自定义方法或验证类做跨场景逻辑复用
有些校验逻辑在多个场景下都要用,但参数或行为略有差异,比如:
- 注册和编辑都要检查邮箱是否已被他人占用,但注册时查
User表,编辑时还要排除自己 - 不同场景对密码强度要求不同:注册要含大小写+数字,重置只需 6 位以上
这时推荐写独立验证类,通过 public 属性传参:
class EmailExistValidator extends Validator
{
public $excludeSelf = false;
public $userIdAttribute = 'id';
public function validateAttribute($model, $attribute)
{
$query = User::find()->where([$attribute => $model->$attribute]);
if ($this->excludeSelf && $model->{$this->userIdAttribute}) {
$query->andWhere(['!=', 'id', $model->{$this->userIdAttribute}]);
}
if ($query->exists()) {
$model->addError($attribute, '该邮箱已被使用');
}
}
}
在 rules 中按需调用:
['email', EmailExistValidator::class, 'on' => 'signup'], ['email', EmailExistValidator::class, 'excludeSelf' => true, 'on' => 'update'],
避免常见陷阱
实际开发中容易卡在这几个点上:
- 没覆盖
scenarios()方法,只靠on标记规则——结果字段赋值失败(load()不识别未声明的属性) -
when回调里用了未加载的关联属性,导致 N+1 或空值误判,建议先判断isset($model->xxx) - 自定义方法忘记加
public修饰符,或签名写成function($attribute)缺少$model参数,直接静默失效 - 需要空值也校验(如“状态为1时,即使 reason 为空也要报错”),却没设
'skipOnEmpty' => false


















