eloquent无“attribute query states”概念,本质是通过getattribute(走accessor/cast)、getoriginal(原始快照)、getraworiginal(未cast原始值)等方法组合判断属性来源与变化轨迹。

PHP中Eloquent的getAttribute和getOriginal怎么区分状态?
直接说结论:Eloquent没有叫「Attribute Query States」的内置概念,所谓“属性查询状态”实际是开发者对getAttribute、getOriginal、isDirty、wasChanged等方法组合使用的俗称。它本质是判断一个模型属性当前值的来源与变化轨迹——比如是从数据库读的、刚被setAttribute改过的、还是经过cast或accessor处理过的。
常见错误现象:$user->name返回的是修改后值,但$user->getOriginal('name')还是旧值,有人误以为这是“缓存未刷新”,其实是Eloquent明确记录了原始快照。
-
getAttribute('name'):走accessor → cast → 值转换流程,返回最终对外暴露的值 -
getOriginal('name'):只读取构造时从数据库加载的原始值(不含cast,也不触发accessor) -
getRawOriginal('name'):连cast都不走,返回DB里原样存的值(比如JSON字段存的是字符串"{\"a\":1}") - 调用
fresh()会重置original,但不会清空changes数组;syncOriginal()才是手动同步原始值
Laravel CQRS里怎么避免在Query Model中误用save()或setAttribute()?
CQRS要求Query Model只读,但Eloquent模型默认可写。如果把一个用于查询的UserReadModel实例不小心调了save(),它真会发UPDATE语句——哪怕你只打算读数据。
使用场景:做报表、后台列表、API只读接口时,应切断写能力,而非靠文档或约定来约束。
- 最简单做法:继承
Illuminate\Database\Eloquent\Model后重写save()、delete()、update()等方法,统一抛出RuntimeException - 更彻底的方式:不继承Eloquent Model,改用
DB::table('users')->select(...)->get()+stdClass或自定义DTO类,完全脱离ORM生命周期 - 若仍需Eloquent语法糖(如
withCount),可用new static构造空白模型再setRawAttributes()注入查询结果,确保$model->exists === false,这样save()会强制走INSERT而非UPDATE
为什么isDirty('status')在retrieved事件后立刻为true?
这不是bug,是Eloquent设计使然:当模型从数据库加载完成触发retrieved事件时,original已设好,但attributes和original此时完全一致,isDirty()应为false。如果你观察到true,大概率是以下原因之一:
- 某个
accessor或mutator在retrieved钩子中偷偷调了setAttribute('status', ...),导致changes被写入 - 字段用了
cast(比如'status' => 'integer'),而DB里存的是字符串'1',cast后变成整数1,Eloquent认为值“变了”(类型不同) - 数据库字段默认值被ORM解析成
null,但实际DB返回的是空字符串'',cast后不等价,触发dirty标记
验证方式:在retrieved事件回调里打日志,输出$model->getAttributes()和$model->getOriginal()对比结果。
Query Model里用whereHas查关联,性能掉得厉害怎么办?
whereHas底层是LEFT JOIN + 子查询或EXISTS,当关联表数据量大、没建对索引时,很容易全表扫描。CQRS场景下尤其敏感,因为Query Model本就不该承担复杂关系计算。
- 优先用
whereExists替代whereHas,它生成EXISTS子查询,通常比JOIN更轻量 - 给外键字段加索引:比如
posts.user_id必须有索引,否则whereHas('user')必然慢 - 避免嵌套
whereHas(如whereHas('author.posts.tags')),拆成多次查询+内存过滤更可控 - 考虑冗余字段:在主表加
has_published_posts布尔字段,用Observer监听Post状态变更时更新,查询时直接where('has_published_posts', true)
真正难处理的不是语法,而是忘记Query Model的边界——它不该替Domain Model做业务规则判断,比如“用户是否满足VIP条件”这种逻辑,应该由Domain Service算好后写入专门的查询视图或物化字段。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











