各位观众老爷们,大家好! 欢迎来到今天的“解耦大师”系列讲座。 今天我们要聊的是PHP开发中一对好基友:Repository Pattern
(仓储模式) 和 Unit of Work
(工作单元模式)。 这俩哥们儿凑一块儿,能把你的领域层和持久化层彻底解耦,让你的代码更灵活、更易于测试、更优雅。
开场白:代码的耦合之痛
有没有遇到过这样的情况:你的业务逻辑代码里,到处都是数据库操作? 就像下面这样:
<?php
class UserService {
public function registerUser(string $username, string $password): bool {
$db = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'password');
$stmt = $db->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$stmt->execute([$username, password_hash($password, PASSWORD_DEFAULT)]);
// ... 各种业务逻辑 ...
return true;
}
}
这段代码问题大了!
- 耦合严重:
UserService
直接依赖于数据库连接和SQL语句。 如果你想换个数据库,或者修改SQL语句,就得改UserService
的代码。 - 难以测试: 你怎么测试
registerUser
方法? 必须连接数据库,插入数据,这简直是噩梦。 - 违反单一职责原则:
UserService
既负责业务逻辑,又负责数据库操作,身兼数职,累不累啊!
是时候引入Repository Pattern
和 Unit of Work
来拯救我们了!
第一幕:Repository Pattern — 领域对象的守护神
Repository Pattern
的核心思想是:在领域层和数据访问层之间引入一个抽象层。 它就像一个中介,领域层通过它来获取和存储数据,而无需关心底层数据访问的细节。
1.1 定义Repository接口
首先,我们需要定义一个Repository接口。 这个接口定义了领域对象需要的所有数据访问操作。
<?php
interface UserRepository {
public function findById(int $id): ?User;
public function findByUsername(string $username): ?User;
public function save(User $user): void;
public function delete(User $user): void;
public function getAll(): array;
}
这个接口定义了User
对象的CRUD (Create, Read, Update, Delete) 操作。
1.2 实现Repository接口
接下来,我们需要实现这个接口。 这个实现类负责具体的数据库操作。
<?php
class MySQLUserRepository implements UserRepository {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function findById(int $id): ?User {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$data) {
return null;
}
return new User($data['id'], $data['username'], $data['password']);
}
public function findByUsername(string $username): ?User {
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$data) {
return null;
}
return new User($data['id'], $data['username'], $data['password']);
}
public function save(User $user): void {
if ($user->getId()) {
// Update existing user
$stmt = $this->db->prepare("UPDATE users SET username = ?, password = ? WHERE id = ?");
$stmt->execute([$user->getUsername(), $user->getPassword(), $user->getId()]);
} else {
// Create new user
$stmt = $this->db->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$stmt->execute([$user->getUsername(), $user->getPassword()]);
$user->setId((int)$this->db->lastInsertId()); //设置新用户的ID
}
}
public function delete(User $user): void {
$stmt = $this->db->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$user->getId()]);
}
public function getAll(): array {
$stmt = $this->db->prepare("SELECT * FROM users");
$stmt->execute();
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$users = [];
foreach ($data as $row) {
$users[] = new User($row['id'], $row['username'], $row['password']);
}
return $users;
}
}
这个MySQLUserRepository
类实现了UserRepository
接口,并使用PDO来连接MySQL数据库。
1.3 修改UserService
现在,我们可以修改UserService
类,让它使用UserRepository
来访问数据。
<?php
class UserService {
private UserRepository $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function registerUser(string $username, string $password): bool {
$existingUser = $this->userRepository->findByUsername($username);
if ($existingUser) {
// 用户名已存在
return false;
}
$user = new User(null, $username, password_hash($password, PASSWORD_DEFAULT)); // ID will be auto-generated
$this->userRepository->save($user);
// ... 各种业务逻辑 ...
return true;
}
public function getUserById(int $id): ?User {
return $this->userRepository->findById($id);
}
// ... 其他业务逻辑 ...
}
看到了吗? UserService
不再直接依赖于数据库,而是依赖于UserRepository
接口。 这样一来,我们就可以轻松地更换数据库实现,或者使用mock对象进行单元测试。
1.4 Repository Pattern的优点
- 解耦: 领域层和数据访问层彻底解耦,互不影响。
- 可测试性: 可以使用mock对象来模拟Repository,轻松进行单元测试。
- 可维护性: 更换数据库实现,只需要修改Repository实现类,无需修改领域层代码。
- 可重用性: Repository可以被多个服务类重用,减少代码重复。
第二幕:Unit of Work — 事务的掌控者
Unit of Work
的核心思想是:将多个数据操作组合成一个逻辑单元,要么全部成功,要么全部失败。 它就像一个事务管理器,确保数据的一致性。
2.1 定义UnitOfWork接口
首先,我们需要定义一个UnitOfWork接口。
<?php
interface UnitOfWork {
public function registerNew(object $object): void;
public function registerDirty(object $object): void;
public function registerDeleted(object $object): void;
public function commit(): void;
public function rollback(): void;
}
registerNew()
: 注册一个新创建的对象,需要在commit
时插入数据库。registerDirty()
: 注册一个被修改的对象,需要在commit
时更新数据库。registerDeleted()
: 注册一个被删除的对象,需要在commit
时从数据库删除。commit()
: 提交所有注册的操作,执行数据库事务。rollback()
: 回滚所有注册的操作,撤销数据库事务。
2.2 实现UnitOfWork接口
接下来,我们需要实现这个接口。 这个实现类负责管理事务和执行数据操作。
<?php
class MySQLUnitOfWork implements UnitOfWork {
private PDO $db;
private array $newObjects = [];
private array $dirtyObjects = [];
private array $deletedObjects = [];
public function __construct(PDO $db) {
$this->db = $db;
}
public function registerNew(object $object): void {
$this->newObjects[] = $object;
}
public function registerDirty(object $object): void {
$this->dirtyObjects[] = $object;
}
public function registerDeleted(object $object): void {
$this->deletedObjects[] = $object;
}
public function commit(): void {
$this->db->beginTransaction();
try {
foreach ($this->newObjects as $object) {
// 根据对象类型调用相应的Repository的save方法
if ($object instanceof User) {
$userRepository = new MySQLUserRepository($this->db); // Or inject the repository
$userRepository->save($object);
} // Add other object types and their repositories here
}
foreach ($this->dirtyObjects as $object) {
// 根据对象类型调用相应的Repository的save方法
if ($object instanceof User) {
$userRepository = new MySQLUserRepository($this->db); // Or inject the repository
$userRepository->save($object);
} // Add other object types and their repositories here
}
foreach ($this->deletedObjects as $object) {
// 根据对象类型调用相应的Repository的delete方法
if ($object instanceof User) {
$userRepository = new MySQLUserRepository($this->db); // Or inject the repository
$userRepository->delete($object);
} // Add other object types and their repositories here
}
$this->db->commit();
$this->clear(); // 清空所有注册的对象
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
public function rollback(): void {
$this->db->rollBack();
$this->clear(); // 清空所有注册的对象
}
private function clear(): void {
$this->newObjects = [];
$this->dirtyObjects = [];
$this->deletedObjects = [];
}
}
这个MySQLUnitOfWork
类实现了UnitOfWork
接口,并使用PDO来管理事务。
2.3 修改UserService
现在,我们可以修改UserService
类,让它使用UnitOfWork
来管理事务。
<?php
class UserService {
private UserRepository $userRepository;
private UnitOfWork $unitOfWork;
public function __construct(UserRepository $userRepository, UnitOfWork $unitOfWork) {
$this->userRepository = $userRepository;
$this->unitOfWork = $unitOfWork;
}
public function registerUser(string $username, string $password): bool {
$existingUser = $this->userRepository->findByUsername($username);
if ($existingUser) {
// 用户名已存在
return false;
}
$user = new User(null, $username, password_hash($password, PASSWORD_DEFAULT));
$this->unitOfWork->registerNew($user); // 注册新用户
try {
$this->unitOfWork->commit(); // 提交事务
} catch (Exception $e) {
$this->unitOfWork->rollback(); // 回滚事务
// ... 异常处理 ...
return false;
}
// ... 各种业务逻辑 ...
return true;
}
public function updateUserPassword(int $userId, string $newPassword): bool {
$user = $this->userRepository->findById($userId);
if (!$user) {
return false; // 用户不存在
}
$user->setPassword(password_hash($newPassword, PASSWORD_DEFAULT));
$this->unitOfWork->registerDirty($user); // 注册修改的用户
try {
$this->unitOfWork->commit(); // 提交事务
return true;
} catch (Exception $e) {
$this->unitOfWork->rollback(); // 回滚事务
// ... 异常处理 ...
return false;
}
}
// ... 其他业务逻辑 ...
}
看到了吗? UserService
不再直接执行数据库操作,而是将操作注册到UnitOfWork
中,然后由UnitOfWork
来管理事务。 这样一来,我们可以确保数据的一致性,即使在多个数据操作失败的情况下也能回滚事务。
2.4 Unit of Work的优点
- 事务管理: 确保数据的一致性,要么全部成功,要么全部失败。
- 解耦: 业务逻辑与事务管理解耦,代码更清晰。
- 可测试性: 可以使用mock对象来模拟UnitOfWork,轻松进行单元测试。
- 性能优化: 可以将多个数据操作合并成一个事务,减少数据库交互次数。
第三幕:Repository Pattern + Unit of Work — 无敌组合
Repository Pattern
和 Unit of Work
结合使用,可以达到最佳效果。 Repository Pattern
负责数据访问的抽象,Unit of Work
负责事务的管理。 它们协同工作,让你的代码更灵活、更易于测试、更优雅。
3.1 完整示例
下面是一个完整的示例,展示了如何使用Repository Pattern
和 Unit of Work
来实现用户注册功能。
<?php
// User.php (领域对象)
class User {
private ?int $id;
private string $username;
private string $password;
public function __construct(?int $id, string $username, string $password) {
$this->id = $id;
$this->username = $username;
$this->password = $password;
}
public function getId(): ?int {
return $this->id;
}
public function setId(int $id): void {
$this->id = $id;
}
public function getUsername(): string {
return $this->username;
}
public function setUsername(string $username): void {
$this->username = $username;
}
public function getPassword(): string {
return $this->password;
}
public function setPassword(string $password): void {
$this->password = $password;
}
}
// UserRepository.php (Repository 接口)
interface UserRepository {
public function findById(int $id): ?User;
public function findByUsername(string $username): ?User;
public function save(User $user): void;
public function delete(User $user): void;
}
// MySQLUserRepository.php (Repository 实现类)
class MySQLUserRepository implements UserRepository {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function findById(int $id): ?User {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$data) {
return null;
}
return new User($data['id'], $data['username'], $data['password']);
}
public function findByUsername(string $username): ?User {
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$data) {
return null;
}
return new User($data['id'], $data['username'], $data['password']);
}
public function save(User $user): void {
if ($user->getId()) {
// Update existing user
$stmt = $this->db->prepare("UPDATE users SET username = ?, password = ? WHERE id = ?");
$stmt->execute([$user->getUsername(), $user->getPassword(), $user->getId()]);
} else {
// Create new user
$stmt = $this->db->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$stmt->execute([$user->getUsername(), $user->getPassword()]);
$user->setId((int)$this->db->lastInsertId()); //设置新用户的ID
}
}
public function delete(User $user): void {
$stmt = $this->db->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$user->getId()]);
}
}
// UnitOfWork.php (Unit of Work 接口)
interface UnitOfWork {
public function registerNew(object $object): void;
public function registerDirty(object $object): void;
public function registerDeleted(object $object): void;
public function commit(): void;
public function rollback(): void;
}
// MySQLUnitOfWork.php (Unit of Work 实现类)
class MySQLUnitOfWork implements UnitOfWork {
private PDO $db;
private array $newObjects = [];
private array $dirtyObjects = [];
private array $deletedObjects = [];
public function __construct(PDO $db) {
$this->db = $db;
}
public function registerNew(object $object): void {
$this->newObjects[] = $object;
}
public function registerDirty(object $object): void {
$this->dirtyObjects[] = $object;
}
public function registerDeleted(object $object): void {
$this->deletedObjects[] = $object;
}
public function commit(): void {
$this->db->beginTransaction();
try {
foreach ($this->newObjects as $object) {
// 根据对象类型调用相应的Repository的save方法
if ($object instanceof User) {
$userRepository = new MySQLUserRepository($this->db); // Or inject the repository
$userRepository->save($object);
} // Add other object types and their repositories here
}
foreach ($this->dirtyObjects as $object) {
// 根据对象类型调用相应的Repository的save方法
if ($object instanceof User) {
$userRepository = new MySQLUserRepository($this->db); // Or inject the repository
$userRepository->save($object);
} // Add other object types and their repositories here
}
foreach ($this->deletedObjects as $object) {
// 根据对象类型调用相应的Repository的delete方法
if ($object instanceof User) {
$userRepository = new MySQLUserRepository($this->db); // Or inject the repository
$userRepository->delete($object);
} // Add other object types and their repositories here
}
$this->db->commit();
$this->clear(); // 清空所有注册的对象
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
public function rollback(): void {
$this->db->rollBack();
$this->clear(); // 清空所有注册的对象
}
private function clear(): void {
$this->newObjects = [];
$this->dirtyObjects = [];
$this->deletedObjects = [];
}
}
// UserService.php (服务类)
class UserService {
private UserRepository $userRepository;
private UnitOfWork $unitOfWork;
public function __construct(UserRepository $userRepository, UnitOfWork $unitOfWork) {
$this->userRepository = $userRepository;
$this->unitOfWork = $unitOfWork;
}
public function registerUser(string $username, string $password): bool {
$existingUser = $this->userRepository->findByUsername($username);
if ($existingUser) {
// 用户名已存在
return false;
}
$user = new User(null, $username, password_hash($password, PASSWORD_DEFAULT));
$this->unitOfWork->registerNew($user); // 注册新用户
try {
$this->unitOfWork->commit(); // 提交事务
} catch (Exception $e) {
$this->unitOfWork->rollback(); // 回滚事务
// ... 异常处理 ...
return false;
}
// ... 各种业务逻辑 ...
return true;
}
}
// 使用示例
try {
$db = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'password');
$userRepository = new MySQLUserRepository($db);
$unitOfWork = new MySQLUnitOfWork($db);
$userService = new UserService($userRepository, $unitOfWork);
$result = $userService->registerUser('testuser', 'password123');
if ($result) {
echo "用户注册成功!";
} else {
echo "用户注册失败!";
}
} catch (PDOException $e) {
echo "数据库连接失败:" . $e->getMessage();
} catch (Exception $e) {
echo "发生错误:" . $e->getMessage();
}
3.2 注意事项
- 依赖注入: 使用依赖注入来管理Repository和UnitOfWork的依赖关系,可以提高代码的可测试性和可维护性。
- 泛型Repository: 可以使用泛型来创建通用的Repository接口和实现类,减少代码重复。
- 事件驱动: 可以使用事件驱动来解耦领域逻辑和数据访问逻辑。
- ORM框架: 现代PHP框架(如Laravel, Symfony)通常已经内置了Repository Pattern 和 Unit of Work 的支持,可以简化开发。
第四幕:表格总结
为了方便大家理解,这里用表格总结一下Repository Pattern
和 Unit of Work
的核心概念。
特性 | Repository Pattern | Unit of Work |
---|---|---|
核心思想 | 领域层和数据访问层之间引入抽象层 | 将多个数据操作组合成一个逻辑单元,要么全部成功,要么全部失败 |
主要职责 | 封装数据访问逻辑,提供领域对象的数据访问接口 | 管理事务,确保数据的一致性 |
优点 | 解耦、可测试性、可维护性、可重用性 | 事务管理、解耦、可测试性、性能优化 |
适用场景 | 需要解耦领域层和数据访问层,需要进行单元测试,需要更换数据库实现,需要多个服务类共享数据访问逻辑 | 需要管理事务,需要确保数据的一致性,需要在多个数据操作失败的情况下回滚事务 |
典型方法 | findById() , findByUsername() , save() , delete() |
registerNew() , registerDirty() , registerDeleted() , commit() , rollback() |
尾声:解耦的艺术
Repository Pattern
和 Unit of Work
是PHP开发中非常有用的设计模式。 它们可以帮助你解耦领域层和数据访问层,让你的代码更灵活、更易于测试、更优雅。 当然,使用这些模式也会增加代码的复杂性。 因此,在实际开发中,需要根据项目的具体情况来权衡利弊,选择最合适的方案。
希望今天的讲座对大家有所帮助。 下次再见!