本文目录导读:

在领域驱动设计(DDD)中,实体(Entity) 和 值对象(Value Object) 是两个核心概念,它们在PHP中的实现差异主要体现在标识(Identity)、可变性(Mutability) 和 相等性比较(Equality) 上。
以下是它们在PHP中的核心区别及代码示例:
核心区别总览
| 维度 | 实体 (Entity) | 值对象 (Value Object) |
|---|---|---|
| 标识 | 拥有唯一标识(如ID),即使属性全变,仍是同一个对象。 | 没有自己的标识,由其属性值定义。 |
| 相等性 | 通过ID比较是否相等。 | 通过所有属性值比较是否相等。 |
| 可变性 | 通常是可变的(Model),可以修改属性。 | 通常是不可变的,修改属性应返回新实例。 |
| 生命周期 | 有明确的生命周期(创建、更新、删除)。 | 无生命周期,是瞬时的,可随时创建和丢弃。 |
| 例子 | 用户、订单、文章 | 金额、地址、颜色、坐标、日期范围 |
PHP 实现示例
实体示例:用户(User)
实体的核心是 ID,就算改了名字和邮箱,$user 依然是同一个用户。
<?php
class User
{
// 实体的灵魂:唯一标识
private string $userId;
private string $name;
private string $email;
public function __construct(string $userId, string $name, string $email)
{
// 通常由外部传入或自增,保证唯一
$this->userId = $userId;
$this->name = $name;
$this->email = $email;
}
// 标识是不可变的(ID不变)
public function getUserId(): string
{
return $this->userId;
}
// 实体的属性是可变的:允许改名
public function changeName(string $newName): void
{
$this->name = $newName; // 修改了内部状态,但ID没变
}
// 相等性比较:比较ID
public function equals(?User $other): bool
{
return $other !== null && $this->userId === $other->userId;
}
}
// 使用示例
$user1 = new User('001', '张三', 'zs@example.com');
$user2 = new User('001', '李四', 'ls@example.com');
var_dump($user1->equals($user2)); // true (因为ID都是001)
// 即使属性完全改了,只要ID一样,还是同一个实体
$user1->changeName('王五');
值对象示例:货币金额(Money)
值对象的核心是 属性值组合。10美元 就是一个值,换了一百个 10美元 对象,它们代表的是同一个值。
<?php
class Money
{
// 值对象通常是不可变的:用readonly或private + getter
private readonly float $amount;
private readonly string $currency;
public function __construct(float $amount, string $currency)
{
// 可以在这里做业务校验
if ($amount < 0) {
throw new InvalidArgumentException('金额不能为负');
}
$this->amount = $amount;
$this->currency = strtoupper($currency);
}
// 只读访问
public function getAmount(): float
{
return $this->amount;
}
public function getCurrency(): string
{
return $this->currency;
}
// 相等性比较:比较所有属性
public function equals(?Money $other): bool
{
return $other !== null
&& $this->amount === $other->amount
&& $this->currency === $other->currency;
}
// 重要:修改操作返回新实例,而不是修改原对象
public function add(Money $other): Money
{
// 业务规则:只能同币种相加
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('币种不一致');
}
// 返回一个新的值对象!
return new Money(
$this->amount + $other->amount,
$this->currency
);
}
// 格式化为字符串展示
public function __toString(): string
{
return sprintf('%s %.2f', $this->currency, $this->amount);
}
}
// 使用示例
$money1 = new Money(100.00, 'USD');
$money2 = new Money(100.00, 'USD');
$money3 = new Money(100.00, 'CNY');
var_dump($money1->equals($money2)); // true (属性完全一样)
var_dump($money1->equals($money3)); // false (币种不同)
// 不可变性演示
$result = $money1->add(new Money(50.00, 'USD'));
echo $money1->getAmount(); // 输出 100.00 (原对象没变)
echo $result->getAmount(); // 输出 150.00 (新对象)
实战判断技巧
当你定义一个PHP类时,问自己以下问题:
-
它有一个自然的主键(ID)吗?
- 有 -> 很可能是 实体 (用户ID、订单号)。
- 没有 -> 很可能是 值对象 (地址、坐标)。
-
如果两个对象属性完全一样,它们可以被视为同一个东西吗?
- 可以(“张三住在[北京]” vs “李四也住在[北京]”,两个地址一样,没区别) -> 值对象。
- 不可以(“张三是ID=1” vs “李四也是ID=1”,不可能,ID必须不同) -> 实体。
-
这个对象需要在数据库里有独立的表并追踪变化吗?
- 需要(用户修改了名字,需要记录历史) -> 实体。
- 不需要(订单的地址改了,直接替换值就好,旧地址不会单独被追踪) -> 值对象。
- 实体:有 ID,可变,靠ID区分。
$user->changeName()。 - 值对象:无ID,不可变,靠属性组合区分。
$money->add()返回新Money,$address->changeCity('北京')返回新Address。
在PHP中正确区分它们,能让你的代码更符合业务语义,减少意外的状态共享bug,并提高代码的可测试性。