在使用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()中實現深度複製的最佳技巧,就是結合序列化來確保每個對象互不干擾。雖然需要注意性能和對象結構的限制,但在大多數中小型項目中,這種方式簡單、高效,非常實用。
如果你想進一步優化,還可以探索基於反射的自定義復制策略,但那就是另一個話題了!