它的本质是:**领域实体 (Entity) 是为了在代码中锚定“我是谁” (Identity),而不仅仅是“我有什么” (Attributes)。
- 核心定义:Entity 是一个通过唯一标识符 (ID)来区分的对象。即使它的所有属性都变了,只要 ID 不变,它依然是同一个实体。
- 对比值对象 (Value Object):
- 值对象:由属性值定义。两个地址如果街道、城市一样,它们就是相等的 (
$addr1 == $addr2)。没有独立身份。 - 实体:由ID定义。两个用户即使名字、年龄完全一样,只要 ID 不同,他们就是不同的人 (
$user1 !== $user2)。
- 值对象:由属性值定义。两个地址如果街道、城市一样,它们就是相等的 (
- 存在理由:
- 追踪变化 (Change Tracking):因为实体有唯一身份,系统可以记录“张三”从“单身”变成了“已婚”。如果没有实体概念,这只是两条不同的记录,无法体现演变过程。
- 承载行为 (Behavior Carrier):实体不仅是数据容器,更是业务规则的守护者。它知道如何修改自己的状态才合法(如
User::changeEmail()内部校验格式)。 - 长期持久化 (Long-term Persistence):实体通常对应数据库中的一行,需要在多次请求、甚至数年间保持身份一致。
- 核心逻辑:别把 Entity 当成数据库表的简单映射。它是业务世界中有生命、有历史、有身份的核心角色。数组和 DTO 是死的快照,Entity 是活的参与者。
如果把业务系统比作户籍管理系统:
- 值对象 (VO):是家庭住址。
- “北京市朝阳区xx路1号”。
- 如果两个人住这里,这个地址是一样的。
- 地址本身没有“身份证”,它依附于人。
- 领域实体 (Entity):是公民本人。
- 每个人有唯一的身份证号 (ID)。
- 即使他改了名字、换了住址、长了皱纹(属性变化),他的身份证号不变,他依然是同一个人。
- 警察(系统)通过身份证号追踪他的犯罪记录、婚姻状况(状态历史)。
- 核心逻辑:Entity 提供了时间的连续性。它让系统记得“过去的那个他”和“现在的他是同一个”。
一、身份与连续性:为什么 ID 如此重要?
1. 唯一性约束 (Uniqueness Constraint)
- 机制:Entity 必须有一个全局或聚合内唯一的 ID。
- 价值:
- 在分布式系统中,ID 是跨服务引用同一事物的唯一依据。
- 在数据库中,ID 是主键,确保数据不重复。
- 代码体现:
if ($user1->getId() === $user2->getId()) { ... }
2. 状态演变 (State Evolution)
- 场景:订单状态流转
Created -> Paid -> Shipped -> Completed。 - Entity 作用:
- 订单实体持有当前状态。
- 它提供方法
pay(),内部检查是否处于Created状态,然后转为Paid。 - 价值:保证了状态变更的合法性和可追溯性。如果是纯数据数组,任何人都可以把状态改成
Completed而跳过Paid,导致业务漏洞。
3. 相等性判断 (Equality Check)
- 规则:实体的相等性只比较 ID,不比较其他属性。
publicfunctionequals(self$other):bool{return$this->id===$other->id;} - 价值:即使两个用户对象是从不同查询加载的,属性略有差异(如一个加载了详情,一个没加载),只要 ID 相同,业务上就视为同一人。
💡 核心洞察:Entity 是时间轴上的一个点。它连接了过去、现在和未来。ID 是那条贯穿时间的线。
二、行为封装:Entity 不是数据结构
1. 保护不变量 (Protecting Invariants)
- 原则:Entity 的任何公共方法执行后,对象必须处于合法状态。
- 示例:
classOrder{publicfunctionaddItem(Product$product):void{if($this->status!==OrderStatus::CREATED){thrownewLogicException("Cannot add items to a completed order.");}$this->items[]=$product;$this->recalculateTotal();// 自动维护一致性}} - 价值:将业务规则内聚在实体内部,防止外部代码破坏业务逻辑。
2. 领域事件发布 (Domain Event Publishing)
- 场景:用户注册成功后,需要发送欢迎邮件、增加积分。
- Entity 作用:
publicfunctionregister():void{// ... 逻辑$this->recordEvent(newUserRegistered($this->id));} - 价值:实体不仅改变自身状态,还通知外界“我发生了变化”,实现解耦。
3. 延迟加载与关联管理
- 场景:获取用户时,不立即加载其所有订单。
- Entity 作用:内部持有代理对象,只有在访问
$user->getOrders()时才触发查询。 - 价值:优化性能,同时保持对象模型的完整性。
三、与 ORM 模型的区别:为什么不能直接用 Eloquent/Doctrine?
很多开发者混淆了ORM 模型和领域实体。
| 维度 | ORM 模型 (Active Record/Data Mapper) | 领域实体 (Domain Entity) |
|---|---|---|
| 主要职责 | 数据持久化 (CRUD) | 业务逻辑与规则 |
| 依赖 | 依赖数据库连接/框架 | 无外部依赖 (Pure PHP) |
| 关注点 | 怎么存 (How to Save) | 是什么 (What it Is) |
| 方法 | save(),delete(),find() | changePassword(),activate() |
| 测试 | 需集成测试 (连库) | 单元测试 (内存中) |
| 最佳实践 | 作为基础设施层 | 作为领域层核心 |
现代架构建议:
- 分离:领域实体应该是纯粹的 PHP 对象,不包含 SQL 逻辑。
- 映射:使用 Repository 模式,将 ORM 模型的数据转换为领域实体,或在实体上使用 Trait 辅助持久化,但尽量保持实体纯净。
- 价值:这样即使更换数据库或 ORM 框架,核心业务逻辑(实体)无需修改。
四、认知牢笼:常见误区
1. 误区:“Entity 就是数据库表的一行。”
- 真相:
- 表是存储结构,Entity 是业务概念。
- 一个 Entity 可能映射到多张表(如继承策略),或多张表组成一个 Entity。
- 对策:从业务语言命名 Entity,而非数据库表名。
2. 误区:“所有对象都是 Entity。”
- 真相:
- 如果对象没有唯一身份,且由其属性定义相等性,它是值对象 (Value Object)(如金钱、颜色、坐标)。
- 对策:区分 Entity 和 VO。VO 更轻量,不可变,适合计算。
3. 误区:“Entity 应该包含所有业务逻辑。”
- 真相:
- 涉及多个实体交互的逻辑(如转账:从 A 扣钱,给 B 加钱)应放在领域服务 (Domain Service)中。
- 对策:Entity 处理单个个体的规则,Service 处理群体协作。
4. 误区:“Entity 必须有 Getter/Setter。”
- 真相:
- 过度暴露 Getter/Setter 会导致贫血模型。
- 对策:提供意图明确的方法(如
promoteToAdmin()而非setRole('admin'))。
5. 误区:“PHP 性能差,不适合搞复杂的 Entity。”
- 真相:
- 现代 PHP + OPcache 性能足够支撑大多数业务。
- 清晰的 Entity 模型能减少 Bug,降低维护成本,这比微小的 CPU 节省更有价值。
- 对策:合理设计聚合根,避免加载过大的对象图。
🚀 总结:原子化“领域实体”全景图
| 维度 | 关键点 |
|---|---|
| 本质 | 通过唯一 ID 标识的业务对象,具有连续性和状态 |
| 核心价值 | 身份追踪、业务规则封装、状态合法性保障 |
| 关键特征 | 唯一 ID、基于 ID 的相等性、可变状态、行为方法 |
| 与 VO 区别 | Entity 看 ID,VO 看值 |
| 与 ORM 区别 | Entity 关注业务,ORM 关注持久化 |
| PHP 隐喻 | Citizen with ID Card (Entity) vs. Home Address (VO) |
| 公式 | Identity = (Unique_ID × State_History) ^ Business_Rules |
终极心法:
领域实体的本质,是“业务世界的锚点”。
它在流动的数据中,确立了不变的自我。
它让系统拥有了记忆,让逻辑拥有了归属。
于身份中见连续,于行为中见规则;以实体为尺,解离散之牛,于领域建模中,求真实之真。
行动指令:
- 识别实体:在你的项目中,找出哪些对象有唯一 ID 且状态会变(如 User, Order, Product)。
- 提取行为:检查这些对象的 Setter,看能否将其重构为更具语义的方法(如
cancel()替代setStatus('cancelled'))。 - 区分 VO:找出哪些对象只是值的组合(如 Money, DateRange),将其改为不可变的值对象。
- 思维升级:记住,Entity 是你业务核心的代言人。保护好它的不变量,就是保护好业务的底线。