PHP实体与值对象怎么定义

wen PHP项目 1

本文目录导读:

PHP实体与值对象怎么定义

  1. 实体(Entity)
  2. 值对象(Value Object)
  3. 常用的模板方法
  4. 实际应用示例
  5. 选择标准
  6. 最佳实践

在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代码更加符合领域驱动设计的理念,提高代码的可维护性和可测试性。

抱歉,评论功能暂时关闭!