Eloquent模型强制非数据库字段必填需在setAttribute中拦截或用creating事件校验,或结合FormRequest补充运行时验证;fillable仅控批量赋值,不校验是否为空。

PHP中Eloquent模型如何强制属性存在(非数据库字段)
PHP里Eloquent本身不校验非数据库字段(即 $casts 或 getXXXAttribute 定义的访问器属性)是否存在,所谓“强制必填”必须靠手动干预。Laravel不会自动报错说 title 属性没传——哪怕你在 setTitleAttribute 里写了逻辑,只要没调用它,就不会触发。
在setAttribute中拦截缺失值并抛异常
最直接可控的方式,是在模型的 setAttribute 方法里做前置检查。注意:这不是覆盖 __set,而是重写 Eloquent 原生的 setAttribute,它会在所有赋值路径($model->xxx = $val、fill()、create())中统一经过。
常见错误现象:create(['name' => 'foo']) 不报错,但业务上要求必须传 slug;或者 API 接收 JSON 后直接 new Model($data),漏掉关键字段却静默通过。
实操建议:
- 在模型中重写
setAttribute,对已知必填属性名做白名单判断 - 仅检查
$key是否在白名单且$value为null或空字符串(根据业务定) - 避免检查所有属性——否则会误伤可为空的数据库字段
- 抛出
InvalidArgumentException,比返回 false 更利于调试
public function setAttribute($key, $value)
{
$requiredAttributes = ['slug', 'status_code'];
if (in_array($key, $requiredAttributes) && ($value === null || $value === '')) {
throw new \InvalidArgumentException("Attribute '{$key}' is required and cannot be empty.");
}
parent::setAttribute($key, $value);
}
用boot() + static::creating/created等事件补位
如果属性是只读计算字段(比如依赖其他字段拼接的 full_name),不适合在 setAttribute 拦截,就得换时机——在模型持久化前校验最终状态。
使用场景:你允许临时设置部分字段,但入库前必须确保某组合完整,例如 start_date 和 end_date 必须同时存在。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
实操建议:
- 在模型
boot()中监听creating事件(仅新建时)或saving(新建+更新) - 用
$this->getAttribute('xxx')获取当前值,避免直接读属性引发无限递归 - 不要在事件里改
$this->xxx后再return false,Laravel 5.8+ 已不支持中断保存,应抛异常 - 若需兼容旧版或自定义响应,可配合表单请求类
FormRequest做前置校验,更清晰
和 Laravel 表单验证器(FormRequest)怎么配合
单纯靠模型层拦截,对 API 场景不够友好——前端需要明确的 422 和字段级错误信息。FormRequest 是更标准的入口守门员。
性能影响很小,但要注意两点:
- 模型层的
setAttribute校验仍有必要:防止绕过 FormRequest 直接 new Model() 的情况(如队列任务、Tinker 调试) - FormRequest 的
rules()里不能写'slug' => 'required|string'如果slug不是数据库字段——Eloquent 会忽略它,验证通过后依然可能存空模型 - 正确做法:在
withValidator里补充运行时检查,或把必填逻辑下沉到模型的save()方法中
容易被忽略的地方是:很多人以为加了 fillable 就等于字段受控,其实 fillable 只控制 mass assignment 白名单,和“是否为空”完全无关。真正要卡住必填,得在数据落地前那一瞬间动手——不是定义时,是赋值/保存时。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










