本文目录导读:

我来详细讲解PHP中如何实现六边形架构(Hexagonal Architecture),也被称为端口与适配器架构。
六边形架构核心概念
基本结构
┌─────────────────────────────────────────────────────┐
│ 外部世界 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ HTTP │ │ CLI │ │ Message │ │
│ │ 适配器 │ │ 适配器 │ │ 适配器 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼──────────────┼──────────────┼───────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────┐
│ 六边形边界(端口) │
│ ┌─────────────────────────────────────────────┐ │
│ │ 应用核心(领域) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 用例 │ │ 实体 │ │ 服务 │ │ │
│ │ │ (UseCase)│ │(Entities)│ │(Services)│ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ 领域模型与业务规则 │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 数据库 │ │ 外部API │ │ 文件系统│
│ 适配器 │ │ 适配器 │ │ 适配器 │
└─────────┘ └─────────┘ └─────────┘
实战示例:订单管理系统
目录结构
project/
├── src/
│ ├── Domain/ # 领域层
│ │ ├── Model/ # 实体和值对象
│ │ │ ├── Order.php
│ │ │ ├── OrderId.php
│ │ │ └── Money.php
│ │ ├── Repository/ # 仓储接口(端口)
│ │ │ └── OrderRepository.php
│ │ ├── Service/ # 领域服务
│ │ │ └── OrderValidationService.php
│ │ └── Event/ # 领域事件
│ │ └── OrderCreated.php
│ │
│ ├── Application/ # 应用层
│ │ ├── UseCase/ # 用例(输入端口)
│ │ │ ├── CreateOrderUseCase.php
│ │ │ └── GetOrderUseCase.php
│ │ └── DTO/ # 数据传输对象
│ │ ├── CreateOrderRequest.php
│ │ └── OrderResponse.php
│ │
│ ├── Infrastructure/ # 基础设施层(适配器)
│ │ ├── Persistence/ # 持久化适配器
│ │ │ ├── DoctrineOrderRepository.php
│ │ │ └── MySQLOrderRepository.php
│ │ ├── Http/ # 输入适配器
│ │ │ ├── OrderController.php
│ │ │ └── OrderRequestValidator.php
│ │ └── Notification/ # 输出适配器
│ │ ├── EmailNotifier.php
│ │ └── SMSNotifier.php
│ │
│ └── Shared/ # 共享模块
│ └── Exception/
│ └── OrderNotFoundException.php
│
└── config/ # 配置目录
└── dependencies.php
核心代码实现
领域层代码
<?php
// src/Domain/Model/Order.php
declare(strict_types=1);
namespace App\Domain\Model;
use App\Domain\Event\OrderCreated;
use DateTimeImmutable;
class Order
{
private OrderId $id;
private string $customerName;
private Money $totalAmount;
private array $items = [];
private string $status;
private DateTimeImmutable $createdAt;
private array $events = [];
private function __construct(
OrderId $id,
string $customerName,
Money $totalAmount,
array $items,
DateTimeImmutable $createdAt
) {
$this->id = $id;
$this->customerName = $customerName;
$this->totalAmount = $totalAmount;
$this->items = $items;
$this->status = 'pending';
$this->createdAt = $createdAt;
}
// 静态工厂方法
public static function create(
OrderId $id,
string $customerName,
array $items,
Money $amount
): self {
$order = new self(
$id,
$customerName,
$amount,
$items,
new DateTimeImmutable()
);
// 记录领域事件
$order->recordEvent(new OrderCreated($id, $customerName, $amount));
return $order;
}
public function confirm(): void
{
if ($this->status !== 'pending') {
throw new \DomainException('Only pending orders can be confirmed.');
}
$this->status = 'confirmed';
}
public function cancel(): void
{
if ($this->status === 'shipped') {
throw new \DomainException('Shipped orders cannot be cancelled.');
}
$this->status = 'cancelled';
}
// 拉取并清除领域事件
public function pullEvents(): array
{
$events = $this->events;
$this->events = [];
return $events;
}
private function recordEvent(object $event): void
{
$this->events[] = $event;
}
// Getters...
public function getId(): OrderId { return $this->id; }
public function getCustomerName(): string { return $this->customerName; }
public function getTotalAmount(): Money { return $this->totalAmount; }
public function getStatus(): string { return $this->status; }
public function getItems(): array { return $this->items; }
public function getCreatedAt(): DateTimeImmutable { return $this->createdAt; }
}
// src/Domain/Model/OrderId.php
class OrderId
{
private string $value;
private function __construct(string $value)
{
if (!preg_match('/^ORD-\d+$/', $value)) {
throw new \InvalidArgumentException('Invalid Order ID format.');
}
$this->value = $value;
}
public static function generate(): self
{
return new self('ORD-' . uniqid());
}
public static function fromString(string $id): self
{
return new self($id);
}
public function toString(): string
{
return $this->value;
}
}
// src/Domain/Model/Money.php
class Money
{
private float $amount;
private string $currency;
public function __construct(float $amount, string $currency = 'CNY')
{
if ($amount < 0) {
throw new \InvalidArgumentException('Money cannot be negative.');
}
$this->amount = $amount;
$this->currency = $currency;
}
public function getAmount(): float { return $this->amount; }
public function getCurrency(): string { return $this->currency; }
}
端口(接口)定义
<?php
// src/Domain/Repository/OrderRepository.php
namespace App\Domain\Repository;
use App\Domain\Model\Order;
use App\Domain\Model\OrderId;
// 这是输出端口(Outbound Port)
interface OrderRepository
{
public function save(Order $order): void;
public function findById(OrderId $orderId): ?Order;
public function findByCustomerName(string $customerName): array;
public function delete(OrderId $orderId): void;
}
// src/Domain/Repository/OrderNotifier.php
interface OrderNotifier
{
public function notifyOrderCreated(Order $order): void;
public function notifyOrderConfirmed(Order $order): void;
public function notifyOrderCancelled(Order $order): void;
}
应用层用例实现
<?php
// src/Application/UseCase/CreateOrderUseCase.php
namespace App\Application\UseCase;
use App\Application\DTO\CreateOrderRequest;
use App\Application\DTO\OrderResponse;
use App\Domain\Model\Order;
use App\Domain\Model\OrderId;
use App\Domain\Model\Money;
use App\Domain\Repository\OrderRepository;
use App\Domain\Repository\OrderNotifier;
// 这是输入端口(Inbound Port)的实现
class CreateOrderUseCase
{
private OrderRepository $orderRepository;
private OrderNotifier $orderNotifier;
public function __construct(
OrderRepository $orderRepository,
OrderNotifier $orderNotifier
) {
$this->orderRepository = $orderRepository;
$this->orderNotifier = $orderNotifier;
}
public function execute(CreateOrderRequest $request): OrderResponse
{
// 计算订单总金额
$totalAmount = 0;
foreach ($request->getItems() as $item) {
$totalAmount += $item['price'] * $item['quantity'];
}
// 创建订单
$order = Order::create(
OrderId::generate(),
$request->getCustomerName(),
$request->getItems(),
new Money($totalAmount)
);
// 保存订单
$this->orderRepository->save($order);
// 触发通知
foreach ($order->pullEvents() as $event) {
$this->orderNotifier->notifyOrderCreated($order);
}
return OrderResponse::fromOrder($order);
}
}
// src/Application/UseCase/GetOrderUseCase.php
class GetOrderUseCase
{
private OrderRepository $orderRepository;
public function __construct(OrderRepository $orderRepository)
{
$this->orderRepository = $orderRepository;
}
public function execute(string $orderId): OrderResponse
{
$order = $this->orderRepository->findById(OrderId::fromString($orderId));
if (!$order) {
throw new OrderNotFoundException("Order {$orderId} not found.");
}
return OrderResponse::fromOrder($order);
}
}
DTO定义
<?php
// src/Application/DTO/CreateOrderRequest.php
namespace App\Application\DTO;
class CreateOrderRequest
{
private string $customerName;
private array $items;
public function __construct(string $customerName, array $items)
{
$this->customerName = $customerName;
$this->items = $items;
}
public function getCustomerName(): string { return $this->customerName; }
public function getItems(): array { return $this->items; }
}
// src/Application/DTO/OrderResponse.php
class OrderResponse
{
private string $id;
private string $customerName;
private float $totalAmount;
private string $status;
private string $createdAt;
private function __construct(string $id, string $customerName, float $totalAmount, string $status, string $createdAt)
{
$this->id = $id;
$this->customerName = $customerName;
$this->totalAmount = $totalAmount;
$this->status = $status;
$this->createdAt = $createdAt;
}
public static function fromOrder(Order $order): self
{
return new self(
$order->getId()->toString(),
$order->getCustomerName(),
$order->getTotalAmount()->getAmount(),
$order->getStatus(),
$order->getCreatedAt()->format('Y-m-d H:i:s')
);
}
// Getters...
public function toArray(): array
{
return get_object_vars($this);
}
}
适配器实现(输出适配器)
<?php
// src/Infrastructure/Persistence/DoctrineOrderRepository.php
namespace App\Infrastructure\Persistence;
use App\Domain\Model\Order;
use App\Domain\Model\OrderId;
use App\Domain\Repository\OrderRepository;
// 输出适配器:持久化到数据库
class DoctrineOrderRepository implements OrderRepository
{
private $entityManager;
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
public function save(Order $order): void
{
$this->entityManager->persist($order);
$this->entityManager->flush();
}
public function findById(OrderId $orderId): ?Order
{
return $this->entityManager->find(Order::class, $orderId->toString());
}
public function findByCustomerName(string $customerName): array
{
$queryBuilder = $this->entityManager->createQueryBuilder();
return $queryBuilder
->select('o')
->from(Order::class, 'o')
->where('o.customerName = :name')
->setParameter('name', $customerName)
->getQuery()
->getResult();
}
public function delete(OrderId $orderId): void
{
$order = $this->findById($orderId);
if ($order) {
$this->entityManager->remove($order);
$this->entityManager->flush();
}
}
}
// src/Infrastructure/Notification/EmailNotifier.php
namespace App\Infrastructure\Notification;
use App\Domain\Model\Order;
use App\Domain\Repository\OrderNotifier;
// 输出适配器:发送邮件通知
class EmailNotifier implements OrderNotifier
{
private $emailService;
public function __construct($emailService)
{
$this->emailService = $emailService;
}
public function notifyOrderCreated(Order $order): void
{
$this->emailService->send(
$order->getCustomerName(),
'订单创建成功',
sprintf('您的订单 %s 已创建,总金额 %.2f 元',
$order->getId()->toString(),
$order->getTotalAmount()->getAmount()
)
);
}
public function notifyOrderConfirmed(Order $order): void
{
$this->emailService->send(
$order->getCustomerName(),
'订单已确认',
sprintf('您的订单 %s 已确认', $order->getId()->toString())
);
}
public function notifyOrderCancelled(Order $order): void
{
$this->emailService->send(
$order->getCustomerName(),
'订单已取消',
sprintf('您的订单 %s 已取消', $order->getId()->toString())
);
}
}
输入适配器(HTTP Controller)
<?php
// src/Infrastructure/Http/OrderController.php
namespace App\Infrastructure\Http;
use App\Application\DTO\CreateOrderRequest;
use App\Application\UseCase\CreateOrderUseCase;
use App\Application\UseCase\GetOrderUseCase;
use App\Shared\Exception\OrderNotFoundException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
// 输入适配器:HTTP接口
class OrderController
{
private CreateOrderUseCase $createOrderUseCase;
private GetOrderUseCase $getOrderUseCase;
public function __construct(
CreateOrderUseCase $createOrderUseCase,
GetOrderUseCase $getOrderUseCase
) {
$this->createOrderUseCase = $createOrderUseCase;
$this->getOrderUseCase = $getOrderUseCase;
}
public function createOrder(Request $request, Response $response): Response
{
try {
$data = json_decode($request->getBody()->getContents(), true);
// 验证输入
$this->validateOrderRequest($data);
// 创建请求DTO
$createRequest = new CreateOrderRequest(
$data['customer_name'],
$data['items']
);
// 执行用例
$result = $this->createOrderUseCase->execute($createRequest);
// 返回成功响应
$response->getBody()->write(json_encode([
'status' => 'success',
'data' => $result->toArray()
]));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus(201);
} catch (\Exception $e) {
return $this->errorResponse($response, $e->getMessage(), 400);
}
}
public function getOrder(Request $request, Response $response, array $args): Response
{
try {
$result = $this->getOrderUseCase->execute($args['id']);
$response->getBody()->write(json_encode([
'status' => 'success',
'data' => $result->toArray()
]));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus(200);
} catch (OrderNotFoundException $e) {
return $this->errorResponse($response, $e->getMessage(), 404);
} catch (\Exception $e) {
return $this->errorResponse($response, '服务器内部错误', 500);
}
}
private function validateOrderRequest(array $data): void
{
if (!isset($data['customer_name']) || empty($data['customer_name'])) {
throw new \InvalidArgumentException('客户名称不能为空');
}
if (!isset($data['items']) || !is_array($data['items']) || empty($data['items'])) {
throw new \InvalidArgumentException('订单项目不能为空');
}
}
private function errorResponse(Response $response, string $message, int $status): Response
{
$response->getBody()->write(json_encode([
'status' => 'error',
'message' => $message
]));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus($status);
}
}
依赖注入与配置
<?php
// config/dependencies.php
use App\Application\UseCase\CreateOrderUseCase;
use App\Application\UseCase\GetOrderUseCase;
use App\Infrastructure\Persistence\DoctrineOrderRepository;
use App\Infrastructure\Notification\EmailNotifier;
use App\Infrastructure\Http\OrderController;
$container = new \DI\Container();
// 配置依赖注入
$container->set('entityManager', function () {
// Laravel/Lumen 中可以用 DB::connection()->getPdo()
// Symfony 中可以用 $entityManager
return require_once 'config/doctrine.php';
});
$container->set('emailService', function () {
// 配置邮件服务
return new \SomeEmailService([
'host' => $_ENV['SMTP_HOST'],
'port' => $_ENV['SMTP_PORT'],
]);
});
// 注入适配器
$container->set(OrderRepository::class, function ($c) {
return new DoctrineOrderRepository($c->get('entityManager'));
});
$container->set(OrderNotifier::class, function ($c) {
return new EmailNotifier($c->get('emailService'));
});
// 注入用例
$container->set(CreateOrderUseCase::class, function ($c) {
return new CreateOrderUseCase(
$c->get(OrderRepository::class),
$c->get(OrderNotifier::class)
);
});
$container->set(GetOrderUseCase::class, function ($c) {
return new GetOrderUseCase($c->get(OrderRepository::class));
});
// 注入控制器
$container->set(OrderController::class, function ($c) {
return new OrderController(
$c->get(CreateOrderUseCase::class),
$c->get(GetOrderUseCase::class)
);
});
return $container;
使用示例
<?php
// public/index.php (入口文件)
require 'vendor/autoload.php';
require 'config/dependencies.php';
// 路由配置(类似 Laravel 路由)
$app = new \SomeFramework\App($container);
// 创建订单
$app->post('/orders', function ($request, $response) use ($container) {
$controller = $container->get(OrderController::class);
return $controller->createOrder($request, $response);
});
// 查看订单
$app->get('/orders/{id}', function ($request, $response, $args) use ($container) {
$controller = $container->get(OrderController::class);
return $controller->getOrder($request, $response, $args);
});
$app->run();
六边形架构的优缺点
✅ 优点
- 分离关注点:业务逻辑与外部依赖完全分离
- 可测试性:可以轻松进行单元测试,不需要外部基础设施
- 可维护性:修改外部依赖不影响核心业务逻辑
- 技术灵活性:可以随时替换数据库、消息系统等
- 独立开发:团队可以并行开发不同部分
❌ 缺点
- 复杂度增加:需要更多的抽象和接口
- 代码量增加:需要更多的 DTO、接口、适配器等
- 学习曲线:团队成员需要理解架构模式
- 过度设计风险:对于简单应用可能过于复杂
实际应用建议
<?php
// 简化版:对于小项目可以这样简化
// 只定义必要的接口,不过度设计
// 1. 定义接口(端口)
interface UserRepository {
public function findById(int $id): ?User;
public function save(User $user): void;
}
interface Mailer {
public function send(string $to, string $subject, string $body): void;
}
// 2. 核心业务逻辑(不依赖任何框架)
class UserService {
public function __construct(
private UserRepository $users,
private Mailer $mailer
) {}
public function register(string $email, string $password): User {
// 业务逻辑
$user = User::create($email, password_hash($password, PASSWORD_BCRYPT));
$this->users->save($user);
$this->mailer->send($email, '欢迎注册', '感谢注册');
return $user;
}
}
// 3. 适配器(具体实现)
class DoctrineUserRepository implements UserRepository {
public function findById(int $id): ?User {
// Doctrine 实现
}
public function save(User $user): void {
// Doctrine 实现
}
}
class SymfonyMailer implements Mailer {
public function send(string $to, string $subject, string $body): void {
// Symfony Mailer 实现
}
}
最佳实践
- 小而精:不需要每个类都加接口,只在真正需要的地方使用
- 依赖注入:使用容器管理依赖关系
- DTO 简洁:避免过度的 DTO 嵌套
- 错误处理:在适配器层处理框架异常,核心层只处理业务异常
- 测试优先:编写单元测试时只测试核心逻辑
六边形架构特别适合中大型应用,尤其是需要频繁变更外部依赖的项目,对于小型应用,可以采用简化版本或 MVP 模式。