本文目录导读:

我来全面讲解PHP项目中标识映射(Identity Map) 与工作单元(Unit of Work) 这两种企业级设计模式。
标识映射(Identity Map)
概念
确保每个数据库记录在内存中只有一个对象实例,避免同一记录被加载多次。
实现示例
<?php
class IdentityMap
{
private array $objects = [];
// 按类名和ID存储对象
public function set(string $class, $id, object $object): void
{
$key = $this->createKey($class, $id);
$this->objects[$key] = $object;
}
// 按类名和ID获取对象
public function get(string $class, $id): ?object
{
$key = $this->createKey($class, $id);
return $this->objects[$key] ?? null;
}
// 检查对象是否已加载
public function has(string $class, $id): bool
{
$key = $this->createKey($class, $id);
return isset($this->objects[$key]);
}
// 移除对象
public function remove(string $class, $id): void
{
$key = $this->createKey($class, $id);
unset($this->objects[$key]);
}
// 清空所有对象
public function clear(): void
{
$this->objects = [];
}
private function createKey(string $class, $id): string
{
return sprintf('%s:%s', $class, $id);
}
}
// 使用示例
class UserRepository
{
private IdentityMap $identityMap;
private PDO $pdo;
public function __construct(PDO $pdo, IdentityMap $identityMap)
{
$this->pdo = $pdo;
$this->identityMap = $identityMap;
}
public function findById(int $id): ?User
{
// 先从标识映射中查找
if ($this->identityMap->has(User::class, $id)) {
echo "从缓存返回\n";
return $this->identityMap->get(User::class, $id);
}
// 从数据库加载
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$data) {
return null;
}
// 创建对象并存入标识映射
$user = new User($data);
$this->identityMap->set(User::class, $id, $user);
echo "从数据库加载\n";
return $user;
}
}
工作单元(Unit of Work)
概念
维护所有被修改的对象列表,并在事务边界统一提交所有变更。
完整实现
<?php
// 对象状态枚举
enum ObjectState: string
{
case CLEAN = 'clean';
case DIRTY = 'dirty'; // 需要更新
case NEW = 'new'; // 需要插入
case REMOVED = 'removed'; // 需要删除
}
// 工作单元实现
class UnitOfWork
{
private IdentityMap $identityMap;
private array $objects = []; // key => {object, state, originalData}
private array $callbacks = [];
public function __construct(IdentityMap $identityMap)
{
$this->identityMap = $identityMap;
}
// 注册新对象
public function registerNew(object $object): void
{
$key = $this->getObjectKey($object);
$this->objects[$key] = [
'object' => $object,
'state' => ObjectState::NEW,
'originalData' => $this->getObjectData($object)
];
}
// 注册脏对象(已修改)
public function registerDirty(object $object): void
{
$key = $this->getObjectKey($object);
if (!isset($this->objects[$key])) {
$this->objects[$key] = [
'object' => $object,
'state' => ObjectState::DIRTY,
'originalData' => $this->getObjectData($object)
];
} elseif ($this->objects[$key]['state'] !== ObjectState::NEW) {
$this->objects[$key]['state'] = ObjectState::DIRTY;
}
}
// 注册删除对象
public function registerRemoved(object $object): void
{
$key = $this->getObjectKey($object);
if (isset($this->objects[$key]) && $this->objects[$key]['state'] === ObjectState::NEW) {
unset($this->objects[$key]); // 如果是新对象,直接移除
return;
}
$this->objects[$key] = [
'object' => $object,
'state' => ObjectState::REMOVED,
'originalData' => $this->getObjectData($object)
];
}
// 注册清理对象
public function registerClean(object $object): void
{
$key = $this->getObjectKey($object);
$this->objects[$key] = [
'object' => $object,
'state' => ObjectState::CLEAN,
'originalData' => $this->getObjectData($object)
];
}
// 提交所有变更
public function commit(Persister $persister): void
{
try {
$persister->beginTransaction();
// 按顺序处理:新增、更新、删除
foreach ($this->objects as $key => $info) {
switch ($info['state']) {
case ObjectState::NEW:
$persister->insert($info['object']);
break;
case ObjectState::DIRTY:
$persister->update($info['object']);
break;
case ObjectState::REMOVED:
$persister->delete($info['object']);
break;
}
}
$persister->commit();
// 提交后,所有对象变为clean状态
foreach ($this->objects as $key => $info) {
if ($info['state'] !== ObjectState::REMOVED) {
$this->registerClean($info['object']);
} else {
unset($this->objects[$key]);
}
}
// 执行回调
foreach ($this->callbacks as $callback) {
$callback();
}
} catch (\Exception $e) {
$persister->rollback();
throw $e;
}
}
// 注册提交后的回调
public function addCommitCallback(callable $callback): void
{
$this->callbacks[] = $callback;
}
private function getObjectKey(object $object): string
{
return spl_object_id($object);
}
private function getObjectData(object $object): array
{
// 实际项目中应该使用反射或序列化
return (array) $object;
}
}
// 持久化器接口
interface Persister
{
public function beginTransaction(): void;
public function commit(): void;
public function rollback(): void;
public function insert(object $object): void;
public function update(object $object): void;
public function delete(object $object): void;
}
// 具体持久化器实现
class DatabasePersister implements Persister
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function beginTransaction(): void
{
$this->pdo->beginTransaction();
}
public function commit(): void
{
$this->pdo->commit();
}
public function rollback(): void
{
$this->pdo->rollBack();
}
public function insert(object $object): void
{
if ($object instanceof User) {
$stmt = $this->pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute([$object->getName(), $object->getEmail()]);
$object->setId((int) $this->pdo->lastInsertId());
}
}
public function update(object $object): void
{
if ($object instanceof User) {
$stmt = $this->pdo->prepare('UPDATE users SET name = ?, email = ? WHERE id = ?');
$stmt->execute([$object->getName(), $object->getEmail(), $object->getId()]);
}
}
public function delete(object $object): void
{
if ($object instanceof User) {
$stmt = $this->pdo->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([$object->getId()]);
}
}
}
组合使用示例
<?php
// 完整的应用服务示例
class UserService
{
private UnitOfWork $unitOfWork;
private UserRepository $repository;
public function __construct(UnitOfWork $unitOfWork, UserRepository $repository)
{
$this->unitOfWork = $unitOfWork;
$this->repository = $repository;
}
// 创建用户
public function createUser(string $name, string $email): User
{
$user = new User(null, $name, $email);
// 注册到工作单元
$this->unitOfWork->registerNew($user);
// 提交事务
$this->unitOfWork->commit(new DatabasePersister(new PDO('...')));
return $user;
}
// 更新用户信息
public function updateUser(int $userId, string $name, string $email): void
{
// 从标识映射获取用户(会先检查缓存)
$user = $this->repository->findById($userId);
if (!$user) {
throw new \RuntimeException('User not found');
}
// 修改用户属性
$user->setName($name);
$user->setEmail($email);
// 标记为脏对象
$this->unitOfWork->registerDirty($user);
// 批量提交
$this->unitOfWork->commit(new DatabasePersister(new PDO('...')));
}
// 批量操作示例
public function batchUpdate(): void
{
$persister = new DatabasePersister(new PDO('...'));
// 多个修改
$user1 = $this->repository->findById(1);
$user1->setName('New Name 1');
$this->unitOfWork->registerDirty($user1);
$user2 = $this->repository->findById(2);
$this->unitOfWork->registerRemoved($user2);
$user3 = new User(null, 'New User', 'new@email.com');
$this->unitOfWork->registerNew($user3);
// 一次提交所有变更
$this->unitOfWork->commit($persister);
}
}
// 使用示例
try {
$identityMap = new IdentityMap();
$unitOfWork = new UnitOfWork($identityMap);
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$repository = new UserRepository($pdo, $identityMap);
$service = new UserService($unitOfWork, $repository);
// 批量操作
$service->batchUpdate();
echo "所有操作成功完成\n";
} catch (\Exception $e) {
echo "操作失败: " . $e->getMessage() . "\n";
}
高级特性
懒加载与标识映射结合
<?php
class LazyLoader
{
private \Closure $loader;
private bool $loaded = false;
private $value;
public function __construct(\Closure $loader)
{
$this->loader = $loader;
}
public function get()
{
if (!$this->loaded) {
$this->value = ($this->loader)();
$this->loaded = true;
}
return $this->value;
}
}
// 在标识映射中使用懒加载
class ProxyUser extends User
{
private LazyLoader $profileLoader;
public function __construct(int $id)
{
parent::__construct();
$this->setId($id);
// 配置懒加载
$this->profileLoader = new LazyLoader(function () {
// 从数据库加载额外数据
return $this->loadProfileFromDB();
});
}
public function getProfile()
{
return $this->profileLoader->get();
}
}
最佳实践
- 事务边界清晰:工作单元应位于单一事务边界内
- 合理的粒度:不要在一个工作单元中处理过多对象
- 缓存策略:标识映射应与缓存策略结合,考虑过期时间
- 并发控制:考虑乐观锁或悲观锁防止并发问题
- 测试:使用依赖注入方便单元测试
框架中的实现
许多PHP框架已经实现了这些模式:
- Doctrine ORM:完整的标识映射和工作单元实现
- Laravel Eloquent:部分实现了标识映射
- Yii2 ActiveRecord:有自己的工作单元实现
了解这些底层模式有助于更好地理解和使用ORM框架。