Yii2 ActiveRecord自定义验证需分场景:内联验证适合轻量逻辑,签名必须为($model,$attribute,$params),空值默认仍执行;独立验证类需继承Validator并重写validateAttribute(),类名须以Validator结尾;exist验证器要注意targetAttribute字段映射;each验证器用于数组元素批量校验。

Yii2 的 ActiveRecord 模型自带验证能力,但真实业务中常需校验数据库约束之外的逻辑,比如字段组合规则、敏感词拦截、跨表存在性判断等。自定义验证不是“加个函数就行”,得清楚什么时候用内联、什么时候写独立类,以及关键参数怎么控制执行时机。
内联验证器:适合单次、轻量逻辑
直接在 rules() 方法里写匿名函数或引用模型方法,签名必须是 ($model, $attribute, $params):
- 验证失败时调用 $model->addError($attribute, $message),不能 return false 或 throw
- 若字段允许为空(比如没加 required 规则),默认仍会执行;如需跳过空值,显式加 'skipOnEmpty' => true
- 若其他规则已报错(如类型错误),默认不再执行;如需强制运行,加 'skipOnError' => false
示例:密码确认一致校验
public function rules()
{
return [
['password_repeat', function ($model, $attribute) {
if ($model->password !== $model->$attribute) {
$model->addError($attribute, '两次输入的密码不一致');
}
}],
];
}
独立验证类:适合复用、带配置的复杂逻辑
新建一个类,继承 yii\validators\Validator,重写 validateAttribute() 方法:
- 类名必须以 Validator 结尾(如 PhoneValidator),否则自动加载失败
- 需要支持客户端验证?实现 clientValidateAttribute(),返回合法 JS 字符串
- 自定义参数(如 public $country = 'CN')要声明为 public 属性,并在 rules() 中传入
- 别忘了在构造方法里调 parent::init(),否则 $this->message 等基础属性不会初始化
示例:手机号格式校验类
class PhoneValidator extends Validator
{
public $country = 'CN';
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = '{attribute} 不是有效的手机号';
}
}
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if ($this->country === 'CN' && !preg_match('/^1[3-9]\d{9}$/', $value)) {
$model->addError($attribute, $this->message);
}
}
}
在 rules() 中使用:
[['phone'], PhoneValidator::class, 'country' => 'CN']
存在性校验(exist):注意字段映射问题
验证某个 ID 是否在目标表中存在,最常用但也最容易出错:
- 当表单字段名和数据库字段名不一致时(如表单用 senderId,user 表字段是 id),必须通过 targetAttribute 显式指定
- targetClass 必须是 ActiveRecord 类,且对应真实数据表
正确写法:
public function rules()
{
return [
['senderId', 'exist', 'targetClass' => User::class, 'targetAttribute' => 'id'],
['receiverId', 'exist', 'targetClass' => User::class, 'targetAttribute' => 'id'],
];
}
数组元素批量校验:each 验证器
当字段是数组(如 deleteIds = [1, 2, 3]),要确保每个元素都满足某规则:
- rule 值是一个数组,第一个元素是验证器别名(如 'integer'),后续是该验证器的配置项
- 支持嵌套,比如 ['min' => 1] 可直接传进 integer 验证器内部
示例:ID 列表必须全为大于 0 的整数
[['deleteIds', 'each', 'rule' => ['integer', 'min' => 1]]]


















