Eloquent 和“属性投影状态”、“事件溯源视图”这些词放在一起,其实是个挺常见的误解。简单说,Lara vel 的 Eloquent 是典型的 Active Record ORM,它天生就没打算做事件溯源(Event Sourcing)或者投影(Projection)这些事。所谓“属性投影状态”,既不是 Eloquent 的标准特性,也不是 Lara vel 官方文档里能找到的功能——这其实是把不同架构模式的概念混在一起了。

为什么 getAttribute 和访问器(Accessors)不是“投影状态”
有朋友会把自定义的 getFooAttribute 访问器当成“投影”,但这完全是两码事。访问器只是运行时的一个静态计算——比如你写个 getTotalPriceAttribute,它无非是当场把两个字段拼起来算出结果,既不记录变更历史,也不响应任何事件。真正的投影,得监听领域事件、按顺序应用、持久化为只读视图,整个过程 Eloquent 根本不参与。
getTotalPriceAttribute只是运行时拼接字段,不记录变更历史,也不响应事件- 修改模型后调用
$model->total_price,返回的是当前值,不是某次事件后的“状态快照” - 若底层数据被绕过 Eloquent 直接更新(如 SQL UPDATE),访问器结果可能与实际不一致
想实现事件溯源 + 投影,得用额外包或手动编排
在 Lara vel 生态里,spatie/lara vel-event-sourcing 是目前最接近生产可用的事件溯源方案。它提供了 AggregateRoot、事件存储、投影器(Projector)和重放机制。不过要注意,它和 Eloquent 是解耦的——投影器通常写入独立的只读表(比如 order_views),而不是复用 Eloquent 模型的属性逻辑。
- 投影器类继承
Projector,监听OrderPlaced等事件,用DB::table('order_views')->upsert(...)更新视图表 - Eloquent 模型(如
Order)负责写操作;投影表(如OrderView)用普通模型或查询构造器读取,不带业务逻辑 - 不能把
OrderView当成“带投影状态的 Eloquent 模型”——它没有sa ve()、不触发事件、不走访问器
常见误用:在 Eloquent 模型里硬塞事件处理逻辑
比如有人在 Order 的 sa ving 事件里手动调用 OrderViewProjector::onOrderPlaced($this),这种做法后患无穷:
- 事务边界错乱:Eloquent 保存失败时,投影可能已写入(违反一致性)
- 耦合加重:模型直接依赖投影器,测试难、替换难
- 无法重放:历史事件缺失上下文(如用户 ID、IP),因为
$this是当前模型实例,不是原始事件数据 - 性能隐患:每次写都同步触发投影,拖慢主流程;应异步(如 dispatch 到队列)
真正做事件溯源,关键不是“怎么在 PHP 里写个 getter”,而是设计事件结构、选择存储(比如 MySQL 的 event_store 表)、隔离读写模型、保证投影幂等性。Eloquent 可以作为写模型的一部分,但它本身不是投影引擎,也绝不该被当作状态快照容器来用。