在使用 PHP 的 PDOStatement::fetchObject() 方法时,我们通常可以方便地将数据库查询结果直接映射成对象。但是,有时候因为某些特殊需求(比如防止引用问题),我们希望拿到的对象是的副本,而不是直接映射的单实例对象。这篇文章将介绍一种简单高效的方法,实现深度复制的技巧。
正常情况下,fetchObject() 返回的是查询行的新对象,但如果对象内部含有引用属性、或通过其他逻辑缓存了对象副本,可能导致数据泄露或修改冲突。
例如:
$stmt = $pdo->query('SELECT id, name FROM users');
$user = $stmt->fetchObject(User::class);
// 后续对 $user 的修改可能影响到其他逻辑
$user->name = 'New Name';
为了避免这种副作用,我们希望每次取到的对象都是独立无关的,哪怕内部属性有引用,也能正确断开。
PHP 中实现深度复制最直接的方式是通过序列化和反序列化对象。示例如下:
function deepCopy($object) {
return unserialize(serialize($object));
}
这个 deepCopy 函数可以确保对象及其内部属性都被完整复制,不再共享引用。
我们可以稍微包装一下 fetchObject() 的使用流程,使得每次获取对象后,立即进行深度复制:
class UserRepository
{
protected PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function findAllUsers(): array
{
$stmt = $this->pdo->query('SELECT id, name, profile FROM users');
$users = [];
while ($user = $stmt->fetchObject(User::class)) {
$users[] = $this->deepCopy($user);
}
return $users;
}
protected function deepCopy(object $object): object
{
return unserialize(serialize($object));
}
}
在这个例子里,我们使用 deepCopy() 包装了从数据库取回的对象,确保 $users 数组里的每个元素都是独立的副本。
虽然序列化-反序列化的方法简单易用,但也有几个注意点:
性能开销:序列化深度复制对性能有一定影响,尤其在处理大量数据时,要评估好影响。
对象限制:如果对象中包含资源句柄(如数据库连接、文件指针等不可序列化的属性),使用序列化会失败。
自定义复制:如果对象比较复杂,可以考虑实现 __clone() 方法来手动深度复制逻辑。
如果项目中经常需要深度复制对象,可以封装一个工具类:
class ObjectHelper
{
public static function deepCopy(object $object): object
{
return unserialize(serialize($object));
}
}
然后调用时更加清晰:
$copy = ObjectHelper::deepCopy($user);
如果你有兴趣,还可以把这个工具发布成 Composer 包,上传到自己的私有仓库,比如 https://gitbox.net/your-username/object-helper,方便在多个项目中复用。
在 PDOStatement::fetchObject() 中实现深度复制的最佳技巧,就是结合序列化来确保每个对象互不干扰。虽然需要注意性能和对象结构的限制,但在大多数中小型项目中,这种方式简单、高效,非常实用。
如果你想进一步优化,还可以探索基于反射的自定义复制策略,但那就是另一个话题了!