本文目录导读:

- 建模前的思维转变
- 核心战术模式
- 状态管理(对象的状态机)
- 数据持久化的解耦(数据库无关)
- 处理不变量的技术实现
- 领域事件(进阶)
- 实战项目结构模板
- 高性能注意事项(PHP 特性)
- 需要避开的坑(常见误区)
- 学习路径建议
PHP 领域建模是面向对象编程在业务逻辑层的核心实践,它不只是写几个类,而是设计一套能真实反映业务规则、生命周期和约束的系统。
以下是按照从基础到进阶的顺序,梳理的 PHP 领域建模完整方法论:
建模前的思维转变
从 数据驱动 转向 行为驱动
- 错误思维:先设计数据库表,然后写
User类,操作$this->db->update('users', ...)。 - 正确思维:先设计业务动作(如
User::changePassword()),再考虑数据如何持久化。
领域模型不是数据库的映射,它是业务逻辑的载体。
核心战术模式
这些是构成领域模型的基础构件,源自《领域驱动设计》:
实体(Entity)
- 定义:有唯一标识(ID)且生命周期持续的对象。
- 核心:相等性基于 ID,值相同但 ID 不同则是两个不同的实体。
- 示例:
Order,User,Product。
class Order {
public function __construct(
private string $id,
private string $status,
private array $items
) {}
public function id(): string {
return $this->id;
}
// 业务行为:确认订单
public function confirm(): void {
if ($this->status !== 'pending') {
throw new \LogicException('只有待处理的订单才能确认');
}
$this->status = 'confirmed';
// 这里可以触发领域事件
}
}
值对象(Value Object)
- 定义:没有唯一标识,完全由属性值决定的对象。
- 核心:不可变,相等性基于所有属性。
- 示例:
Money,EmailAddress,Address(如果两地址属性相同,它们就是同一个地址概念)。
final class Money {
public function __construct(
private int $amount, // 用整数分存储,避免浮点误差
private string $currency = 'CNY'
) {
if ($amount < 0) throw new \InvalidArgumentException('金额不能为负');
}
public function add(Money $other): Money {
return new Money($this->amount + $other->amount, $this->currency);
}
public function isLessThan(Money $other): bool {
return $this->amount < $other->amount;
}
// 不要提供 setter!不可变对象通过方法返回新实例
}
聚合(Aggregate)
- 定义:一组相关对象的集合,聚合根是外部访问的唯一入口,保证内部一致性。
- 核心:事务边界,修改聚合内部状态必须通过聚合根。
- 示例:
Order(聚合根)包含OrderItem(实体)和Money(值对象),下单时,外部绝对不能直接修改OrderItem的行数,必须通过Order->addItem()。
class Order {
// 聚合根持有生命周期内的实体列表
// 构造函数用 private 防止随意 new,推荐使用工厂方法
private function __construct(
private string $id,
private array $items
) {}
public static function createNew(string $id): self {
return new self($id, []);
}
public function addItem(Product $product, int $quantity): void {
$this->items[] = new OrderItem(
uniqid(),
$product->id(),
$quantity,
$product->price()
);
// 重算总价等业务逻辑
}
}
领域服务(Domain Service)
- 定义:当某个业务操作不属于任何实体或值对象时,放入领域服务。
- 场景:跨聚合的复杂业务(转账涉及两个账户,不属于 Account 或 Money)。
- 注意:领域服务应无状态(不保存数据),只执行操作。
class TransferService {
public function transfer(Money $amount, Account $from, Account $to): void {
$from->withdraw($amount);
$to->deposit($amount);
// 这里即使发生异常,事务管理器会回滚
}
}
仓库(Repository)
- 定义:用于检索和存储聚合根,隐藏底层持久化细节。
- 核心:返回的是领域对象,不是数组。
- 组织:在 PHP 中多定义为接口,持久化实现放在 Infrastructure 层。
interface OrderRepository {
public function findById(string $id): ?Order;
public function save(Order $order): void;
}
// 实现类(放在 Infra 层)
class MysqlOrderRepository implements OrderRepository {
public function findById(string $id): ?Order {
// 查表 -> 组装聚合根对象 -> 返回
}
}
状态管理(对象的状态机)
领域模型内部需管理自身状态生命周期,建议使用 Enum(PHP 8.1+)并显式控制。
// PHP 8.1 枚举
enum OrderStatus: string {
case PENDING = 'pending';
case PAID = 'paid';
case SHIPPED = 'shipped';
case CANCELLED = 'cancelled';
public function canTransitionTo(self $target): bool {
// 定义合法的状态流转矩阵
return match ($this) {
self::PENDING => in_array($target, [self::PAID, self::CANCELLED]),
self::PAID => $target === self::SHIPPED,
default => false,
};
}
}
数据持久化的解耦(数据库无关)
关键原则:不要让 PDO 或 Eloquent 的 save() 污染你的领域模型。
ORM 建模策略(PHP 生态):
-
复杂业务场景:不使用 Eloquent Model 作为领域实体,而是:
- Repository 接口在 Domain 层,实现用 Doctrine 或自定义数据库查询。
- 领域对象是普通 PHP 类,Repository 负责领域对象 <-> 数组/Entity 的转换。
-
简单场景:可以直接用 Repository 隔离。
class MoneyFactory {
public static function fromDatabase(int $cents): Money {
return new Money($cents);
}
}
class UserMapper {
public static function toPersistence(User $user): array {
return ['id' => $user->id(), 'name' => $user->name()->toString()];
}
public static function fromPersistence(array $row): User {
// 组装 User 领域对象
}
}
处理不变量的技术实现
领域模型必须保证业务约束(不变量)在任何时候成立。
技巧:
- 构造器严格校验。
- 方法内部
assert断言业务条件。 - 使用异常捕获业务违规,
DomainException。
class Account {
public function __construct(
private string $id,
private Money $balance
) {
// 业务不变量 1:余额不能为负(构造函数已通过 Money 保证)
}
public function withdraw(Money $amount): void {
// 业务不变量 2:提款不能导致余额为负
if ($amount->isGreaterThan($this->balance)) {
throw new \DomainException("余额不足,无法提款");
}
$this->balance = $this->balance->subtract($amount);
}
}
领域事件(进阶)
用于解耦聚合之间的通信,当状态变更发生时,发布事件,其他服务订阅并响应。
class Order {
public function confirm(): void {
// ... 状态变更逻辑
// 发布事件(假设有一个事件收集器)
$this->events[] = new OrderConfirmed($this->id);
}
public function releaseEvents(): array {
$events = $this->events;
$this->events = [];
return $events;
}
}
实战项目结构模板
src/
├── Domain/ # 领域层(核心业务,不依赖框架)
│ ├── Model/
│ │ ├── Order.php # 聚合根
│ │ ├── OrderItem.php # 实体
│ │ └── Money.php # 值对象
│ ├── Repository/
│ │ └── OrderRepository.php # 接口
│ ├── Service/
│ │ └── TransferService.php # 领域服务
│ └── Event/
│ └── OrderConfirmed.php # 领域事件
├── Application/ # 应用层(用例编排)通常包含 Command/Query
│ └── UseCase/
│ ├── CreateOrder.php
│ └── ConfirmOrder.php
└── Infrastructure/ # 基础设施层(数据库实现)
├── Persistence/
│ ├── DoctrineOrderRepository.php
│ └── Mapper/
│ └── OrderMapper.php
└── Database/
└── Connection.php
高性能注意事项(PHP 特性)
- 使用严格类型:
declare(strict_types=1);避免隐式转换导致的业务 bug。 - PHP 8+ 新特性:
- 构造函数属性提升:
public function __construct(private int $qty) {} - 只读属性(Readonly):值对象不可变的最佳伙伴。
- 枚举:状态机管理。
- 构造函数属性提升:
- 不可变性:返回新对象而非修改原对象,便于并发场景下避免共享状态错误。
- 延迟加载:PHP 不擅长持有大量内存对象,聚合内部如果包含大量集合,应使用
LazyCollection或门面查询,不完全载入内存。
需要避开的坑(常见误区)
| 错误做法 | 正确做法 |
|---|---|
让 Model 继承 Eloquent,然后写业务逻辑进去 |
将业务逻辑移到独立的领域对象中 |
Model 包含 getX() 返回关联数组 |
返回领域对象或值对象 |
| 在控制器中写 if...else 判断状态 | 在领域对象中提供状态机变更方法 |
使用 setStatus() 随意改变状态 |
confirm(), cancel() 来表示意图动作 |
| 为了“复用”在领域对象里塞入 CRUD 方法 | 保持领域对象的单一行为,CRUD 由 Repository 负责 |
学习路径建议
- 先练习:写一个简单的「银行转账」系统,用标准实体 + 值对象实现。
- 引入 Repository:将数据库查询移除出 controller。
- 引入 DSL(领域特定语言):像
$order->applyDiscount(new Percentage(10))这些实际上是在做领域建模。 - 读源码:看
Spatie的DomainEvents包或Broadway(PHP 的 CQRS 框架)怎么处理聚合。
最终总结:领域建模的目标是让代码像说人话,如果你看到一个类和方法,觉得它描述了一个明确的业务动作,那么你已经做对了,例如看到 $order->fulfill() 而不是 $order->setStatus('done')。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播