本文目录导读:

在PHP中,实体(Entity)和值对象(Value Object)是领域驱动设计(DDD)中的两个核心概念,我来详细讲解它们的定义和区别。
实体(Entity)
核心特征
- 有唯一标识:具有ID,用于区分不同对象
- 可变:属性可以修改,但有生命周期
- 相同性:通过ID判断是否相等,而非属性值
基础定义
<?php
// 简单的实体示例
class User
{
private string $id;
private string $name;
private string $email;
private DateTimeImmutable $createdAt;
private DateTimeImmutable $updatedAt;
public function __construct(
string $id,
string $name,
string $email
) {
$this->id = $id;
$this->name = $name;
$this->email = $email;
$this->createdAt = new DateTimeImmutable();
$this->updatedAt = new DateTimeImmutable();
}
// getter方法
public function getId(): string
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
// 实体方法 - 修改行为
public function changeName(string $newName): void
{
$this->name = $newName;
$this->updatedAt = new DateTimeImmutable();
}
// 相等性比较
public function equals(self $other): bool
{
return $this->id === $other->id;
}
}
带业务逻辑的实体
<?php
class Order
{
private string $id;
private array $items;
private string $status;
private float $totalAmount;
public function __construct(string $id)
{
$this->id = $id;
$this->items = [];
$this->status = 'pending';
$this->totalAmount = 0;
}
public function addItem(OrderItem $item): void
{
$this->items[] = $item;
$this->recalculateTotal();
}
public function confirm(): void
{
if ($this->status !== 'pending') {
throw new DomainException('只能确认待处理的订单');
}
$this->status = 'confirmed';
}
private function recalculateTotal(): void
{
$this->totalAmount = array_sum(
array_map(fn($item) => $item->getTotal(), $this->items)
);
}
}
值对象(Value Object)
核心特征
- 无唯一标识:没有ID
- 不可变:创建后不能修改
- 相同性:通过所有属性值判断是否相等
- 自包含:作为整体使用
基础定义
<?php
// 简单的值对象示例
class Money
{
private float $amount;
private string $currency;
public function __construct(float $amount, string $currency)
{
if ($amount < 0) {
throw new InvalidArgumentException('金额不能为负数');
}
$this->amount = $amount;
$this->currency = $currency;
}
public function getAmount(): float
{
return $this->amount;
}
public function getCurrency(): string
{
return $this->currency;
}
// 值对象方法 - 返回新实例
public function add(Money $other): Money
{
if ($this->currency !== $other->currency) {
throw new DomainException('不能添加不同货币的金额');
}
return new Money(
$this->amount + $other->amount,
$this->currency
);
}
// 保证不可变性
public function withAmount(float $newAmount): Money
{
return new Money($newAmount, $this->currency);
}
// 相等性比较
public function equals(Money $other): bool
{
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
public function __toString(): string
{
return sprintf('%s %s', number_format($this->amount, 2), $this->currency);
}
}
复杂值对象
<?php
// 地址值对象
class Address
{
private string $street;
private string $city;
private string $state;
private string $postalCode;
public function __construct(
string $street,
string $city,
string $state,
string $postalCode
) {
$this->street = $street;
$this->city = $city;
$this->state = $state;
$this->postalCode = $postalCode;
$this->validate();
}
private function validate(): void
{
if (strlen($this->street) < 5) {
throw new InvalidArgumentException('街道地址太短');
}
if (!preg_match('/^[A-Za-z0-9-]+$/', $this->postalCode)) {
throw new InvalidArgumentException('无效的邮政编码');
}
}
// 所有属性都是只读的
public function getStreet(): string
{
return $this->street;
}
public function getCity(): string
{
return $this->city;
}
public function getState(): string
{
return $this->state;
}
public function getPostalCode(): string
{
return $this->postalCode;
}
// 完整相等性比较
public function equals(Address $other): bool
{
return $this->street === $other->street
&& $this->city === $other->city
&& $this->state === $other->state
&& $this->postalCode === $other->postalCode;
}
}
常用的模板方法
实体基类模板
<?php
abstract class BaseEntity
{
protected string $id;
protected DateTimeImmutable $createdAt;
protected DateTimeImmutable $updatedAt;
abstract public function getId(): string;
protected function __construct()
{
$this->createdAt = new DateTimeImmutable();
$this->updatedAt = new DateTimeImmutable();
}
protected function markUpdated(): void
{
$this->updatedAt = new DateTimeImmutable();
}
public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;
}
public function getUpdatedAt(): DateTimeImmutable
{
return $this->updatedAt;
}
}
值对象基类模板
<?php
abstract class BaseValueObject
{
abstract public function equals($other): bool;
public function __toString(): string
{
return json_encode($this->toArray());
}
abstract protected function toArray(): array;
}
实际应用示例
实体中的值对象
<?php
class Product extends BaseEntity
{
private string $name;
private Money $price; // 值对象
private Address $warehouseAddress; // 值对象
private string $sku;
public function __construct(
string $id,
string $name,
Money $price,
Address $warehouseAddress
) {
parent::__construct();
$this->id = $id;
$this->name = $name;
$this->price = $price;
$this->warehouseAddress = $warehouseAddress;
$this->sku = $this->generateSku($name);
}
public function updatePrice(Money $newPrice): void
{
if ($newPrice->getAmount() < $this->price->getAmount()) {
// 降价的业务逻辑
}
$this->price = $newPrice;
$this->markUpdated();
}
public function getId(): string
{
return $this->id;
}
private function generateSku(string $name): string
{
return strtoupper(substr($name, 0, 3)) . '-' . $this->id;
}
}
// 使用示例
$price = new Money(99.99, 'USD');
$address = new Address('123 Main St', 'New York', 'NY', '10001');
$product = new Product('prod-001', 'Excellent Product', $price, $address);
$product->updatePrice(new Money(89.99, 'USD')); // 实体属性变化
// 值对象是不可变的
$newPrice = new Money(79.99, 'USD');
// 不能直接修改 $price->amount,而是创建新的Money实例
选择标准
什么时候用实体?
<?php
// ✅ 应使用实体的情况
class User
{
public function changeEmail(string $newEmail): void
{
// 它需要跟踪身份,属性可以变化
}
}
class Question
{
public function updateContent(string $newContent): void
{
// 它有ID,可以修改
}
}
什么时候用值对象?
<?php
// ✅ 应使用值对象的情况
class Color
{
private string $hexValue;
// 颜色没有身份,值相同就是同一个颜色
}
class Coordinates
{
private float $latitude;
private float $longitude;
// 坐标是整体,不可变
}
class EmailAddress
{
private string $email;
// 验证逻辑封装在内部
}
最佳实践
<?php
// 1. 实体:负责身份和生命周期管理
final class Customer extends BaseEntity
{
public function __construct(string $id)
{
parent::__construct();
$this->id = $id;
}
public function getId(): string
{
return $this->id;
}
// 业务行为
}
// 2. 值对象:封装值而不变性
final class FullName extends BaseValueObject
{
public function __construct(
private readonly string $firstName,
private readonly string $lastName
) {}
public function getFullName(): string
{
return "{$this->firstName} {$this->lastName}";
}
public function equals($other): bool
{
if (!$other instanceof self) {
return false;
}
return $this->firstName === $other->firstName
&& $this->lastName === $other->lastName;
}
protected function toArray(): array
{
return [
'firstName' => $this->firstName,
'lastName' => $this->lastName
];
}
}
| 特征 | 实体 | 值对象 |
|---|---|---|
| 标识 | 必须有ID | 不需要ID |
| 可变性 | 可变的 | 不可变的 |
| 相等性 | 通过ID | 通过所有属性 |
| 生命周期 | 有自己的生命周期 | 依附于实体 |
| 存储 | 通常存在数据库表中 | 通常作为实体的属性 |
| 例子 | User, Order, Product | Money, Address, Color |
理解并正确使用实体和值对象,可以让你的PHP代码更加符合领域驱动设计的理念,提高代码的可维护性和可测试性。