本文目录导读:

这是一个非常专业且前瞻性的问题,在PHP项目中应用六边形架构(又称端口适配器模式),是为了解决传统MVC架构中业务逻辑与框架、数据库、外部服务强耦合的问题,从而打造高可维护、可测试、可独立演进的业务核心。
下面我将从核心概念、PHP项目结构示例、关键实现细节以及最佳实践四个维度,为你系统性地拆解。
核心概念回顾
六边形架构的核心思想是:业务逻辑是应用程序的中心(六边形内部),它通过“端口”与外部世界通信,“适配器”则负责将外部输入转化为端口能理解的形式。
- 内部(领域层):纯PHP对象,无框架依赖,无数据库依赖,包含实体、值对象、领域服务、仓库接口。
- 端口:定义在内部层的接口,如
UserRepositoryInterface、SendNotificationInterface。 - 外部(基础设施层):适配器实现,如
MysqlUserRepository、TwilioSmsAdapter。
PHP项目结构示例
假设一个用户注册的场景,项目目录结构如下:
project-root/
├── src/
│ ├── Application/ # 应用层(编排用例)
│ │ ├── UseCase/
│ │ │ └── RegisterUserUseCase.php
│ │ └── DTO/
│ │ └── RegisterUserRequest.php
│ │
│ ├── Domain/ # 六边形内部 - 核心
│ │ ├── Entity/
│ │ │ └── User.php
│ │ ├── ValueObject/
│ │ │ └── Email.php
│ │ ├── Repository/
│ │ │ └── UserRepositoryInterface.php # 输出端口
│ │ └── Service/
│ │ └── NotificationServiceInterface.php # 输入端口
│ │
│ └── Infrastructure/ # 六边形外部 - 适配器
│ ├── Persistence/
│ │ └── DoctrineUserRepository.php # 输出适配器
│ ├── Notification/
│ │ └── SendGridNotificationAdapter.php # 输入适配器
│ └── Controller/
│ └── ApiUserController.php # 输入适配器(HTTP端)
│
├── config/
│ └── services.yaml # 依赖注入配置
├── public/
│ └── index.php
└── tests/
└── Unit/
└── Domain/
└── UserTest.php # 纯单元测试,无需数据库
关键代码实现
领域层(六边形核心,不依赖任何外部)
// src/Domain/Entity/User.php
class User {
public function __construct(
private readonly UserId $id,
private readonly Email $email,
private string $name
) {}
public function changeName(string $newName): void {
// 业务规则逻辑
if (strlen($newName) < 2) {
throw new \InvalidArgumentException('Name too short');
}
$this->name = $newName;
}
}
// src/Domain/Repository/UserRepositoryInterface.php (端口)
interface UserRepositoryInterface {
public function save(User $user): void;
public function findByEmail(Email $email): ?User;
}
应用层(用例编排)
// src/Application/UseCase/RegisterUserUseCase.php
class RegisterUserUseCase {
public function __construct(
private UserRepositoryInterface $userRepo,
private NotificationServiceInterface $notifier
) {}
public function execute(RegisterUserRequest $request): void {
// 1. 检查用户是否存在
if ($this->userRepo->findByEmail($request->email)) {
throw new \RuntimeException('User already exists');
}
// 2. 创建领域实体
$user = new User(
UserId::generate(),
$request->email,
$request->name
);
// 3. 持久化
$this->userRepo->save($user);
// 4. 发送通知
$this->notifier->sendWelcomeEmail($user);
}
}
基础设施层(适配器实现)
// src/Infrastructure/Persistence/DoctrineUserRepository.php
class DoctrineUserRepository implements UserRepositoryInterface {
public function __construct(
private EntityManager $em // Doctrine ORM
) {}
public function save(User $user): void {
$this->em->persist($user);
$this->em->flush();
}
public function findByEmail(Email $email): ?User {
return $this->em->getRepository(User::class)
->findOneBy(['email' => (string)$email]);
}
}
// src/Infrastructure/Controller/ApiUserController.php
class ApiUserController {
public function __construct(
private RegisterUserUseCase $useCase
) {}
public function register(Request $request): Response {
$dto = new RegisterUserRequest(
new Email($request->get('email')),
$request->get('name')
);
try {
$this->useCase->execute($dto);
return new Response('OK', 201);
} catch (\Throwable $e) {
return new Response($e->getMessage(), 400);
}
}
}
优势与PHP最佳实践
-
框架无关性:业务逻辑不依赖Laravel/Symfony,你可以先写Domain层,最后再绑定到框架。
-
极致可测试性:
-
测试
RegisterUserUseCase时,只需MockUserRepositoryInterface和NotificationServiceInterface。 -
无需启动数据库、无需HTTP内核。
// tests/Unit/Application/RegisterUserUseCaseTest.php public function test_duplicate_email_throws_exception(): void { $mockRepo = $this->createMock(UserRepositoryInterface::class); $mockRepo->method('findByEmail')->willReturn($this->createMock(User::class)); $useCase = new RegisterUserUseCase($mockRepo, ...); $this->expectException(\RuntimeException::class); $useCase->execute(new RegisterUserRequest(...)); }
-
-
技术栈切换低成本:
- 从MySQL切到PostgreSQL?只需写一个新的
PostgresUserRepository实现UserRepositoryInterface。 - 从SendGrid切到Mailgun?同样只需一个新的适配器。
- 从MySQL切到PostgreSQL?只需写一个新的
常见陷阱与建议
- 避免“贫血领域模型”:不要只把Entity变成简单的getter/setter,业务逻辑应该放在Entity内部,而不是放在Service里。
- 防止端口爆炸:如果一个接口只有一个适配器,且未来几乎不可能变化,过早抽象是浪费,建议“第一次用接口分离,第二次再抽象”。
- 依赖注入是核心:必须使用容器(如PHP-DI、Symfony DI)来注入适配器实现到用例中。避免在Domain层使用Service Locator。
- DTO设计:用例的输入输出尽量使用简单的DTO(Data Transfer Object),不要直接传递HTTP Request对象到应用层,这会破坏六边形架构。
与端口适配器的关系说明
- “端口”:是Domain层定义的接口,如
UserRepositoryInterface,表达“我需要什么”。 - “适配器”:是Infrastructure层的实现,如
DoctrineUserRepository,表达“我用什么技术来满足这个需求”。
在PHP中,端口适配器的本质是“依赖倒置”——高层模块(Domain)不依赖低层模块(Infrastructure),两者都依赖抽象接口。
在PHP项目中实现六边形架构,本质是将业务复杂度与基础设施复杂度解耦,对于中大型团队、长期维护的PHP项目,或者需要高测试覆盖率、多端点(API+CLI+队列)的Web应用,这套架构的投资回报率非常高,但对于简单的CRUD项目,建议谨慎采用,避免过度工程化。
如果你有具体的“如何将现有MVC项目重构为六边形架构”的场景,欢迎进一步交流。