嘿,大家好!今天咱们来聊聊PHP里的“六边形架构”,也叫“端口与适配器模式”。这名字听起来有点玄乎,但其实它就是个让你的代码更灵活、更容易测试、更容易维护的好东西。
1. 啥是六边形架构?(别被名字唬住)
想象一下,你的应用程序是个城堡,城堡里住着你的核心业务逻辑。六边形架构就是想把这个城堡保护起来,让它不受外界风吹雨打的影响。
那怎么保护呢?答案是“端口”和“适配器”。
- 端口 (Port): 端口就是城堡的城门。每个城门只允许特定类型的“货物”进出。比如,一个端口负责接收用户的HTTP请求,另一个端口负责往数据库里存数据。
- 适配器 (Adapter): 适配器就是来搬运“货物”的工人。他们负责把外界的“货物”(比如HTTP请求)翻译成城堡里能理解的语言,或者把城堡里的“货物”(比如处理结果)翻译成外界能理解的语言(比如JSON响应)。
说白了,六边形架构就是把你的核心业务逻辑和外部依赖隔离开来。你的核心业务逻辑通过端口来和外界交流,而适配器负责处理具体的外部依赖。
用人话说:
就好像你是个大老板(核心业务逻辑),你不想直接跟客户(外部依赖)打交道,所以你雇了秘书(适配器)来帮你处理各种事务。秘书负责把客户的要求翻译成你能理解的语言,也负责把你处理的结果翻译成客户能理解的语言。
2. 为什么要用六边形架构?(好处多到你数不过来)
- 可测试性 (Testability): 你的核心业务逻辑不再依赖具体的外部实现,你可以轻松地用模拟 (Mock) 或桩 (Stub) 来测试它,而不用担心外部依赖会影响测试结果。
- 可维护性 (Maintainability): 当你需要更换外部依赖时(比如把MySQL换成PostgreSQL),你只需要修改适配器,而不用修改核心业务逻辑。
- 可扩展性 (Scalability): 你可以根据需要添加新的适配器,而不会影响现有的代码。
- 灵活性 (Flexibility): 你的核心业务逻辑可以适应不同的外部环境。
举个例子:
假设你有个用户注册功能。
- 不用六边形架构: 你的注册逻辑直接依赖
$_POST
获取用户数据,直接依赖MySQL
数据库存储用户数据,直接依赖SwiftMailer
发送验证邮件。如果有一天你想换成Redis
存储用户数据,或者换成SendGrid
发送验证邮件,你就得修改你的注册逻辑。 - 用六边形架构: 你的注册逻辑只依赖一个
UserRegistrationInputPort
接口和一个UserRepositoryPort
接口和一个EmailServicePort
接口。HTTP
适配器负责从$_POST
获取用户数据,并将其传递给UserRegistrationInputPort
。MySQL
适配器负责将用户数据存储到MySQL
数据库。SwiftMailer
适配器负责使用SwiftMailer
发送验证邮件。如果有一天你想换成Redis
,你只需要写一个Redis
适配器,并实现UserRepositoryPort
接口,而不用修改注册逻辑。
3. 代码示例(PHP版,手把手教你)
咱们来用一个简单的例子来说明六边形架构的实现:用户注册。
3.1 定义端口 (Ports)
首先,我们需要定义端口。端口就是接口,它定义了核心业务逻辑需要什么,以及它能提供什么。
<?php
namespace AppPorts;
interface UserRegistrationInputPort
{
public function register(UserRegistrationRequest $request): UserRegistrationResponse;
}
interface UserRepositoryPort
{
public function save(User $user): void;
public function findByEmail(string $email): ?User;
}
interface EmailServicePort
{
public function send(string $to, string $subject, string $body): void;
}
这里我们定义了三个端口:
UserRegistrationInputPort
:负责接收用户注册请求。UserRepositoryPort
:负责存储用户数据。EmailServicePort
:负责发送邮件。
还有两个简单的 value object
<?php
namespace AppPorts;
class UserRegistrationRequest
{
private string $email;
private string $password;
public function __construct(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function getEmail(): string
{
return $this->email;
}
public function getPassword(): string
{
return $this->password;
}
}
<?php
namespace AppPorts;
class UserRegistrationResponse
{
private bool $success;
private ?string $errorMessage;
public function __construct(bool $success, ?string $errorMessage = null)
{
$this->success = $success;
$this->errorMessage = $errorMessage;
}
public function isSuccess(): bool
{
return $this->success;
}
public function getErrorMessage(): ?string
{
return $this->errorMessage;
}
}
3.2 定义实体 (Entities)
实体是核心业务逻辑中的数据对象。
<?php
namespace AppEntities;
class User
{
private int $id;
private string $email;
private string $password;
public function __construct(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function getId(): int
{
return $this->id;
}
public function getEmail(): string
{
return $this->email;
}
public function getPassword(): string
{
return $this->password;
}
public function setId(int $id): void
{
$this->id = $id;
}
}
3.3 定义用例 (Use Cases) / 核心业务逻辑
用例是核心业务逻辑的具体实现。
<?php
namespace AppUseCases;
use AppPortsUserRegistrationInputPort;
use AppPortsUserRegistrationRequest;
use AppPortsUserRegistrationResponse;
use AppPortsUserRepositoryPort;
use AppPortsEmailServicePort;
use AppEntitiesUser;
class UserRegistration implements UserRegistrationInputPort
{
private UserRepositoryPort $userRepository;
private EmailServicePort $emailService;
public function __construct(UserRepositoryPort $userRepository, EmailServicePort $emailService)
{
$this->userRepository = $userRepository;
$this->emailService = $emailService;
}
public function register(UserRegistrationRequest $request): UserRegistrationResponse
{
$email = $request->getEmail();
$password = $request->getPassword();
// 1. 检查邮箱是否已存在
if ($this->userRepository->findByEmail($email)) {
return new UserRegistrationResponse(false, '邮箱已存在');
}
// 2. 创建用户
$user = new User($email, password_hash($password, PASSWORD_BCRYPT));
// 3. 保存用户
$this->userRepository->save($user);
// 4. 发送验证邮件
$this->emailService->send(
$email,
'欢迎注册',
'请点击链接验证您的邮箱'
);
return new UserRegistrationResponse(true);
}
}
注意:UserRegistration
类只依赖 UserRepositoryPort
和 EmailServicePort
接口,而不依赖具体的实现。
3.4 定义适配器 (Adapters)
适配器负责将外部依赖转换为核心业务逻辑可以理解的格式。
<?php
namespace AppAdaptersPersistence;
use AppPortsUserRepositoryPort;
use AppEntitiesUser;
use PDO;
class MySQLUserRepository implements UserRepositoryPort
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function save(User $user): void
{
$stmt = $this->pdo->prepare("INSERT INTO users (email, password) VALUES (?, ?)");
$stmt->execute([$user->getEmail(), $user->getPassword()]);
$user->setId((int)$this->pdo->lastInsertId());
}
public function findByEmail(string $email): ?User
{
$stmt = $this->pdo->prepare("SELECT id, email, password FROM users WHERE email = ?");
$stmt->execute([$email]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
return null;
}
$user = new User($row['email'], $row['password']);
$user->setId($row['id']);
return $user;
}
}
<?php
namespace AppAdaptersEmail;
use AppPortsEmailServicePort;
use Swift_Mailer;
use Swift_Message;
use Swift_SmtpTransport;
class SwiftMailerEmailService implements EmailServicePort
{
private Swift_Mailer $mailer;
public function __construct(string $smtpHost, int $smtpPort, string $smtpUsername, string $smtpPassword)
{
$transport = (new Swift_SmtpTransport($smtpHost, $smtpPort))
->setUsername($smtpUsername)
->setPassword($smtpPassword);
$this->mailer = new Swift_Mailer($transport);
}
public function send(string $to, string $subject, string $body): void
{
$message = (new Swift_Message($subject))
->setFrom(['[email protected]' => 'Example'])
->setTo([$to => $to])
->setBody($body);
$this->mailer->send($message);
}
}
<?php
namespace AppAdaptersHttp;
use AppPortsUserRegistrationInputPort;
use AppPortsUserRegistrationRequest;
class UserRegistrationController
{
private UserRegistrationInputPort $userRegistration;
public function __construct(UserRegistrationInputPort $userRegistration)
{
$this->userRegistration = $userRegistration;
}
public function register(array $request): string
{
$userRegistrationRequest = new UserRegistrationRequest(
$request['email'],
$request['password']
);
$response = $this->userRegistration->register($userRegistrationRequest);
if ($response->isSuccess()) {
return '注册成功';
} else {
return '注册失败:' . $response->getErrorMessage();
}
}
}
这里我们定义了三个适配器:
MySQLUserRepository
:负责将用户数据存储到MySQL
数据库。SwiftMailerEmailService
:负责使用SwiftMailer
发送邮件。UserRegistrationController
:负责接收Http请求,并调用UserRegistrationInputPort
完成用户注册。
3.5 组装 (Composition)
最后,我们需要将所有的组件组装起来。这通常在应用程序的入口点完成。
<?php
use AppAdaptersPersistenceMySQLUserRepository;
use AppAdaptersEmailSwiftMailerEmailService;
use AppUseCasesUserRegistration;
use AppAdaptersHttpUserRegistrationController;
// 1. 创建 MySQL 数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=example', 'root', 'password');
// 2. 创建 MySQLUserRepository 适配器
$userRepository = new MySQLUserRepository($pdo);
// 3. 创建 SwiftMailerEmailService 适配器
$emailService = new SwiftMailerEmailService('smtp.example.com', 587, 'username', 'password');
// 4. 创建 UserRegistration 用例
$userRegistration = new UserRegistration($userRepository, $emailService);
// 5. 创建 UserRegistrationController
$controller = new UserRegistrationController($userRegistration);
// 6. 处理用户注册请求
$request = $_POST; // 假设通过 POST 请求传递用户数据
$result = $controller->register($request);
echo $result; // 输出 "注册成功" 或 "注册失败:邮箱已存在"
4. 测试 (Testing)
六边形架构最大的好处之一就是易于测试。我们可以轻松地使用模拟 (Mock) 或桩 (Stub) 来测试我们的核心业务逻辑。
<?php
namespace TestsUnitUseCases;
use AppUseCasesUserRegistration;
use AppPortsUserRegistrationRequest;
use AppPortsUserRepositoryPort;
use AppPortsEmailServicePort;
use AppEntitiesUser;
use PHPUnitFrameworkTestCase;
class UserRegistrationTest extends TestCase
{
public function testRegisterSuccess(): void
{
// 1. 创建模拟的 UserRepository
$userRepository = $this->createMock(UserRepositoryPort::class);
$userRepository->method('findByEmail')->willReturn(null); // 模拟邮箱不存在
$userRepository->expects($this->once())->method('save')->willReturnCallback(function(User $user) {
$user->setId(1);
}); // 模拟保存用户
// 2. 创建模拟的 EmailService
$emailService = $this->createMock(EmailServicePort::class);
$emailService->expects($this->once())->method('send'); // 模拟发送邮件
// 3. 创建 UserRegistration 用例
$userRegistration = new UserRegistration($userRepository, $emailService);
// 4. 创建 UserRegistrationRequest
$request = new UserRegistrationRequest('[email protected]', 'password');
// 5. 调用 register 方法
$response = $userRegistration->register($request);
// 6. 断言
$this->assertTrue($response->isSuccess());
}
public function testRegisterEmailExists(): void
{
// 1. 创建模拟的 UserRepository
$userRepository = $this->createMock(UserRepositoryPort::class);
$userRepository->method('findByEmail')->willReturn(new User('[email protected]', 'password')); // 模拟邮箱已存在
// 2. 创建模拟的 EmailService
$emailService = $this->createMock(EmailServicePort::class);
$emailService->expects($this->never())->method('send'); // 模拟不发送邮件
// 3. 创建 UserRegistration 用例
$userRegistration = new UserRegistration($userRepository, $emailService);
// 4. 创建 UserRegistrationRequest
$request = new UserRegistrationRequest('[email protected]', 'password');
// 5. 调用 register 方法
$response = $userRegistration->register($request);
// 6. 断言
$this->assertFalse($response->isSuccess());
$this->assertEquals('邮箱已存在', $response->getErrorMessage());
}
}
5. 六边形架构的变体 (Variations)
六边形架构有很多变体,最常见的是:
- 洋葱架构 (Onion Architecture): 洋葱架构是六边形架构的一种变体,它强调核心业务逻辑应该完全独立于外部依赖。
- 清洁架构 (Clean Architecture): 清洁架构也是六边形架构的一种变体,它强调代码的可测试性、可维护性和可扩展性。
6. 何时使用六边形架构?(不是银弹,别乱用)
六边形架构是个好东西,但它不是银弹。不是所有项目都适合使用六边形架构。
- 适合:
- 大型项目
- 需要长期维护的项目
- 需要频繁更换外部依赖的项目
- 需要高度可测试性的项目
- 不适合:
- 小型项目
- 一次性项目
- 不需要长期维护的项目
7. 总结 (Recap)
- 六边形架构是一种架构模式,旨在将核心业务逻辑与外部依赖隔离。
- 六边形架构的核心概念是端口和适配器。
- 六边形架构可以提高代码的可测试性、可维护性、可扩展性和灵活性。
- 六边形架构有很多变体,如洋葱架构和清洁架构。
- 六边形架构不是银弹,需要根据项目的具体情况进行选择。
希望今天的讲解对大家有所帮助!六边形架构是个很强大的工具,但关键在于理解它的思想,并灵活运用。记住,代码是写给人看的,顺便让机器执行一下。写出清晰、易懂、易维护的代码才是最重要的。
现在,大家有什么问题吗?