从分层架构到整洁架构
理解依赖关系为何应指向内部。
从分层架构到整洁架构 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 PHP Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 PHP Academy 课程共包含 4 节课。
为什么采用整洁架构
您已经了解经典的三层 PHP 技术栈:控制器 → 服务 → 仓储 → 数据库。它确实可行,但业务逻辑最终会与 Eloquent、Doctrine、超文本传输协议请求以及框架生命周期耦合。整洁架构反转依赖方向,使您的领域完全不需要了解基础设施。这样做的好处是:用例可测试、适配器可替换,并且代码库能够经受框架升级。
依赖规则
整洁架构只有一条规则:源代码依赖只能指向内层。内层圆圈(实体、用例)绝不能引用外层圆圈(控制器、对象关系映射工具、框架)。在运行时,控制流可以通过接口向外流动;但在编译/导入时,内层绝不能导入任何外层内容。
- 实体:企业规则
- 用例:应用规则
- 适配器:控制器、表示器、网关
- 框架与驱动:数据库、超文本传输协议、网络
一个耦合的分层示例
这是大多数 PHP 应用所采用的服务类型。请注意,领域逻辑与 Eloquent 及超文本传输协议响应纠缠在一起。没有数据库和框架,您无法对折扣规则进行单元测试。
<?php
class OrderService
{
public function place(Request $request)
{
$user = User::find($request->user_id); // Eloquent
$total = 0;
foreach ($request->items as $i) {
$total += Product::find($i['id'])->price * $i['qty'];
}
if ($user->is_vip) {
$total *= 0.9; // business rule trapped in infra code
}
Order::create(['user_id' => $user->id, 'total' => $total]);
return response()->json(['total' => $total]);
}
}实体:无框架领域
实体编码企业范围内的规则,不依赖任何东西。它使用原生 PHP,不包含注解,也不继承对象关系映射工具提供的基类。您可以在测试中完整地构造它。
<?php
final class Money
{
public function __construct(public readonly int $cents) {
if ($cents < 0) throw new InvalidArgumentException('negative money');
}
public function multiply(float $factor): self {
return new self((int) round($this->cents * $factor));
}
}
final class Order
{
/** @param array<int,int> $lineCents */
public function __construct(private array $lineCents, private bool $vip) {}
public function total(): Money {
$sum = array_sum($this->lineCents);
$money = new Money($sum);
return $this->vip ? $money->multiply(0.9) : $money;
}
}
echo (new Order([1000, 2000], true))->total()->cents, PHP_EOL; // 2700用例掌控工作流
用例(交互器)负责编排实体,并且只通过接口(端口)与外部世界通信。它接收请求数据传输对象并返回响应数据传输对象——绝不会返回超文本传输协议对象。
<?php
interface OrderRepository {
public function save(Order $order): void;
}
final class PlaceOrder
{
public function __construct(private OrderRepository $orders) {}
public function execute(array $lineCents, bool $vip): int {
$order = new Order($lineCents, $vip);
$this->orders->save($order);
return $order->total()->cents;
}
}边界就是接口
用例声明它所需的OrderRepository接口。该接口位于内层圆圈;具体的 Eloquent/Doctrine 实现位于外层,并依赖内层。这就是在架构边界上应用依赖倒置原则。
源代码依赖方向:EloquentOrderRepository → OrderRepository(接口),绝不能反过来。
<?php
// Lives in infrastructure layer, points INWARD to the domain interface
final class EloquentOrderRepository implements OrderRepository
{
public function save(Order $order): void {
OrderModel::create(['total' => $order->total()->cents]);
}
}无需基础设施即可测试
由于用例依赖接口,测试可以注入伪实现。不需要数据库,也不需要启动框架——单元测试可以在微秒级完成,并断言纯粹的业务行为。
<?php
final class InMemoryOrders implements OrderRepository {
public array $saved = [];
public function save(Order $o): void { $this->saved[] = $o; }
}
$repo = new InMemoryOrders();
$useCase = new PlaceOrder($repo);
$total = $useCase->execute([1000, 2000], true);
assert($total === 2700);
assert(count($repo->saved) === 1);
echo "PASS total=$total saved=" . count($repo->saved) . PHP_EOL;控制器变成精简适配器
控制器现在是一个适配器:它将超文本传输协议转换为用例调用,再将结果转换回超文本传输协议。它不包含任何业务规则。将表述性状态转移接口替换为 CLI 或队列工作进程时,用例完全无需修改。
<?php
final class OrderController
{
public function __construct(private PlaceOrder $placeOrder) {}
public function store(Request $request): JsonResponse {
$total = $this->placeOrder->execute(
lineCents: $request->input('lineCents'),
vip: (bool) $request->input('vip'),
);
return new JsonResponse(['total' => $total], 201);
}
}尖叫式架构
文件夹结构应该突出领域,而不是突出框架。不要在顶层使用Controllers/、Models/。请按 capability 组织目录,让新成员一眼就能看出应用的用途。
src/Ordering/Domain/— 实体、值对象src/Ordering/Application/— 用例、端口接口src/Ordering/Infrastructure/— Eloquent 仓储、超文本传输协议控制器
每个限界上下文都是一个顶层文件夹;框架位于边缘。
强制执行依赖规则
没有工具支持,纪律会逐渐松弛。请在持续集成中使用deptrac或phparkitect,当领域导入基础设施时让构建失败。这样,这条规则就从代码审查中的希望变成了编译时保证。
# deptrac.yaml
deptrac:
layers:
- name: Domain
collectors: [{ type: directory, value: src/.*/Domain/.* }]
- name: Application
collectors: [{ type: directory, value: src/.*/Application/.* }]
- name: Infrastructure
collectors: [{ type: directory, value: src/.*/Infrastructure/.* }]
ruleset:
Domain: [] # Domain may depend on nothing
Application: [Domain]
Infrastructure: [Application, Domain]使用数据传输对象跨越边界
为了避免实体向外泄漏,跨越边界的数据应以简单的数据传输对象传递,而不是实体或对象关系映射模型。用例返回适配器可以序列化的扁平结构,因此领域对象不会离开核心,外层也无法获得内部状态的访问句柄。
<?php
final class OrderSummary // boundary DTO, no behavior, no domain types
{
public function __construct(
public readonly string $orderId,
public readonly int $totalCents,
) {}
}
final class PlaceOrderV2 {
public function __construct(private OrderRepository $orders) {}
public function execute(array $lineCents, bool $vip): OrderSummary {
$order = new Order($lineCents, $vip);
$this->orders->save($order);
return new OrderSummary('ord_1', $order->total()->cents);
}
}快速检查
依赖规则允许哪一种依赖方向?
回顾
您已经从耦合的分层技术栈转向了整洁架构:
- 依赖规则:源代码依赖只能指向内层。
- 实体使用无框架 PHP 承载企业规则。
- 用例通过端口接口进行编排,返回数据传输对象而不是超文本传输协议对象。
- 控制器和对象关系映射仓储是外层适配器,依赖方向指向内层(DIP)。
- 结构应该突出领域,而 deptrac 等工具会在持续集成中强制执行这条规则。
常见问题解答
「从分层架构到整洁架构」课时是免费的吗?
是的 — 「从分层架构到整洁架构」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。
「从分层架构到整洁架构」这节课中我会学到什么?
理解依赖关系为何应指向内部。 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 PHP Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 PHP Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「从分层架构到整洁架构」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 PHP Academy 课中编写并运行代码吗?
能。每节 PHP Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。