eloquent 模型层不支持原生属性验证,因校验逻辑混入访问器会导致批量赋值中断、脱离 laravel 验证体系且违背单一职责;推荐用 booted() 监听 saving 事件配合 validator 统一校验,或通过 $casts 和 mutators 做类型归一与轻量清洗,业务规则应交由 formrequest、service 或 policy 层处理。

Eloquent 模型层的 Attribute Validation 不是 Laravel 原生支持的功能——你不能直接在模型里用 $rules 或 validate() 自动校验属性赋值,硬加会导致逻辑混乱、验证时机错乱、甚至覆盖原始值。
为什么不能在 setXXXAttribute() 里 throw Exception
很多人试图在访问器里做校验,比如:
public function setPriceAttribute($value)
{
if (!is_numeric($value) || $value attributes['price'] = $value;
}
这看似直观,但实际会引发三类问题:
- 批量赋值(
fill()、create())失败时无法统一收集错误,只能中断执行 - 和 Laravel 的表单验证器(
Validator、FormRequest)完全脱节,无法复用规则、语言包或自定义消息 - ORM 层不该承担业务校验职责;模型应专注数据映射,校验应前置到请求入口或服务层
推荐做法:用 setAttribute() + booted() 配合手动触发验证
若真需在模型保存前强制校验(例如封装成可复用的“安全模型”),应在 booted() 中监听 saving 事件,调用独立验证逻辑:
protected static function booted()
{
static::saving(function ($model) {
$validator = Validator::make($model->getAttributes(), [
'price' => ['required', 'numeric', 'min:0'],
'name' => ['required', 'string', 'max:255'],
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
});
}
注意点:
- 只校验
getAttributes()中已设置的字段,避免对 null 值或未填充字段误报 - 不要在
saving中修改$model->attributes后再校验——可能绕过规则(如字符串转数字后才校验类型) - 异常类型用
ValidationException,Laravel 默认中间件能识别并转为 422 响应
更稳妥的替代方案:用 casts + mutators 做类型归一,校验交给上层
真正该由模型处理的,是数据标准化而非业务规则判断。例如:
protected $casts = [
'price' => 'float',
'published_at' => 'datetime',
];
protected function setPriceAttribute($value)
{
$this->attributes['price'] = round((float) $value, 2);
}
这样做的好处:
-
$casts自动处理类型转换,避免数据库存入字符串导致查询失效 -
mutators可做轻量清洗(如 trim、round),不引入业务逻辑分支 - 把「是否合法」交给
FormRequest或 API Controller 的validate(),错误可集中返回、可本地化、可测试
复杂点在于:模型层没有“验证上下文”。比如「价格在促销期间必须大于 100」这种带条件的规则,绝不能塞进模型——它依赖外部状态(活动开关、时间范围),必须在 Service 或 Policy 层判断。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











