__clone() 是 PHP 對象克隆的核心擴展點。當執行 $obj2 = clone $obj1 時,引擎先執行淺拷貝(標量值拷貝、對象屬性引用拷貝),隨後調用 __clone() 允許開發者修正狀態。本文通過三個遞進案例,徹底講透淺/深拷貝差異、關聯對象深拷貝模式、以及常見陷阱。
$obj2 = clone $obj1; // 內部等效於: $obj2 = $obj1; // 1. 淺拷貝:標量複製,對象屬性共享引用 $obj2->__clone(); // 2. 調用 __clone()(若存在)
__clone()class Fruit {
public $name = '水果';
public $color = '顏色';
}
$apple = new Fruit();
$apple->name = '大蘋果';
$apple->color = '紅色';
$clone = clone $apple; // 淺拷貝
$clone->name = '小蘋果';
$clone->color = '青色';
// 結果:$apple 不受影響(標量獨立)
// 若有對象屬性,則共享同一對象實例
__clone() 修正克隆行為class Fruit {
public $name = '水果';
public $color = '顏色';
public function __clone() {
$this->name = '克隆水果'; // 統一標記為克隆體
}
}
$apple = new Fruit();
$apple->name = '大蘋果';
$apple->color = '紅色';
$clone = clone $apple;
$clone->color = '青色'; // 只改顏色
echo $apple->name; // 大蘋果(原對象保留)
echo $clone->name; // 克隆水果(__clone 覆蓋)
echo $clone->color; // 青色
__clone() 無參數、無返回值當對象包含其他對象屬性時,淺拷貝會導致雙方共享同一關聯對象,修改屬性會相互影響。必須在 __clone() 中手動 clone 關聯對象。
class Account {
public $balance;
public function __construct($b) { $this->balance = $b; }
}
class Person {
public $id;
public $name;
public $account; // Account 對象
public function __construct($name, $age, Account $account) {
$this->name = $name;
$this->account = $account;
}
public function setId($id) { $this->id = $id; }
public function __clone() {
$this->id = 0; // 重置標識
$this->account = clone $this->account; // 深拷貝關鍵!
}
}
// 測試
$person = new Person('peter', 15, new Account(1000));
$person->setId(1);
$person2 = clone $person;
$person2->account->balance = 250; // 只修改克隆體的賬戶
var_dump($person->account->balance); // 1000(原對象不受影響)
var_dump($person2->account->balance); // 250
$this->account = clone $this->account// 共享同一 Account 實例 $person2->account->balance = 250; var_dump($person->account->balance); // 250 ← 原對象被污染!
__sleep/__wakeup 配合對象圖極其複雜時,可考慮:
__clone() 中遞歸遍歷屬性,對所有對象屬性 clone__sleep() 返回需序列化屬性名,__wakeup() 重建資源,配合 serialize/unserialize 實現深拷貝(性能較差)deepcopy 或 cloner(Composer 可用)| 陷阱 | 後果 | 修正 |
|---|---|---|
忘記 clone 關聯對象 | 共享狀態,數據污染 | 在 __clone() 中逐個 clone |
| 克隆含有資源句柄的對象 | 資源雙重釋放/失效 | 實現 __clone() 重新打開資源,或禁用克隆 |
| 單例模式未禁用克隆 | 破壞單例唯一性 | private function __clone() {} |
父類有 __clone() 子類未調用 | 父類修正邏輯失效 | parent::__clone() |
class Singleton {
private static $instance;
private function __construct() {}
private function __clone() {} // 禁用 clone
private function __wakeup() {} // 禁用反序列化
public static function getInstance() {
if (!self::$instance) self::$instance = new self();
return self::$instance;
}
}
clone = 淺拷貝 + __clone()__clone() 中 clone $this->propprivate __clone() 禁用parent::__clone()精準控制 __clone(),是編寫健壯領域模型、避免「神秘數據變更」Bug 的關鍵技能。