Eloquent 的核心是模型生命周期、数据边界与查询意图的交互契约。判断逻辑归属需明确三点:是否该进模型、是否需实时计算、是否须落库;访问器仅用于纯字段转换,关联加载或高开销操作必须用普通方法;JSON 字段须配 casts 避免空指针;属性拦截应重写 getAttributeValue/setAttribute 并严格调用 parent 方法;惰性查询须用 cursor() 或 chunkById(),禁用 LazyCollection::make(Model::get());模型只定义“数据是什么”,不处理“数据意味着什么”。

什么时候该用访问器(getXXXAttribute),什么时候必须写普通方法?
访问器适合纯格式转换或字段组合,比如把 first_name 和 last_name 拼成 full_name;但只要涉及关联加载、外部调用、计算开销或业务规则,就该立刻退到普通方法。
- 常见错误:在
getPredictedCompletionRateAttribute里调用 HTTP API 或反序列化大模型——模板循环 50 次,触发 50 次网络请求或文件读取 - 正确姿势:定义
predictedCompletionRate()方法,内部检查$this->relationLoaded('enrollments'),未加载则抛LogicException,强制调用方显式预加载 - JSON 字段如
learning_features必须配casts:'learning_features' => 'array',否则json_decode(null)导致空指针,PHP 8.3+ 还会抛JsonException
怎么安全拦截属性读写做审计或状态响应?
Eloquent 没有 attributeEntered 这种钩子,所谓“进入退出动作”,本质是状态变更的上下文响应。最直接的方式是重写 getAttributeValue 和 setAttribute,但必须守住两条线:调用 parent::,只对目标字段判断。
- 漏掉
parent::getAttributeValue($key)→ 关系不加载、日期不格式化、getOriginal()失效 - 在
setAttribute里只判断$key === 'status',再用$this->isDirty('status')确认是否真变化,避免重复触发 - 如果目标只是“保存后发通知”,用
static::updated事件比手动拦截更干净,天然支持批量update()和upsert()
大数据量下,LazyCollection 怎么用才不翻车?
LazyCollection::make(Model::get()) 是典型伪惰性——get() 已全量查库、实例化、占满内存,再包一层毫无意义。真正惰性必须从查询执行阶段开始控制。
-
cursor()是 Laravel 8.29+ 推荐方案,依赖有序索引字段(如id或created_at),每次 fetch 小批、边读边实例化、用完即释放 -
chunkById()更兼容复杂查询(含join、groupBy),按主键分片,避免offset/limit在大数据下的性能坍塌 - 绝对不要在
cursor()中用orderByRaw()或隐式排序;也别指望它支持paginate()—— 它天生无偏移概念
with('user')”,而是没想清楚:这个值该由谁负责生成、在什么时机生成、出错了谁兜底。模型不是万能胶,它只该管「数据是什么」,不管「数据意味着什么」。



















