PHP 中對象賦值($b = $a)預設是引用賦值(雙方指向同一對象實例),而非複製。真正的複製需用 clone 關鍵字,觸發 __clone() 魔術方法。本文系統講解淺拷貝、深拷貝、__clone 實現模式、PHP 8.1+ readonly 屬性對拷貝的影響。
| 操作 | 語法 | 行為 | 內存 |
|---|---|---|---|
| 賦值 | $b = $a | 雙方指向同一實例(引用計數+1) | 共享 |
| 淺拷貝 | $b = clone $a(無 __clone) | 新實例,標量屬性複製,對象/資源屬性仍共享引用 | 部分共享 |
| 深拷貝 | $b = clone $a(實現 __clone) | 新實例,所有屬性遞歸完全複製 | 完全隔離 |
<?php
class Address {
public string $city;
public function __construct(string $city) { $this->city = $city; }
}
class User {
public string $name;
public Address $address; // 對象屬性
public function __construct(string $name, Address $address) {
$this->name = $name;
$this->address = $address;
}
}
$addr = new Address('Beijing');
$user1 = new User('Alice', $addr);
$user2 = clone $user1; // 淺拷貝
$user2->name = 'Bob';
$user2->address->city = 'Shanghai'; // 修改共享的 Address 對象
echo $user1->address->city; // Shanghai —— user1 也被影響!
class User {
public string $name;
public Address $address;
public array $tags = []; // 數組屬性(PHP 7+ 數組複製是深拷貝,但最好顯式處理)
public function __construct(string $name, Address $address, array $tags = []) {
$this->name = $name;
$this->address = $address;
$this->tags = $tags;
}
// 魔術方法:clone 時自動調用
public function __clone(): void {
// 1. 對象屬性深拷貝
$this->address = clone $this->address;
// 2. 數組/標量屬性預設已複製,但可顯式處理
// $this->tags = $this->tags; // 可省略
// 3. 資源屬性需特別處理(如重新打開文件句柄)
// if (is_resource($this->fp)) { $this->fp = fopen(...); }
}
}
$addr = new Address('Beijing');
$user1 = new User('Alice', $addr, ['vip', 'beta']);
$user2 = clone $user1;
$user2->name = 'Bob';
$user2->address->city = 'Shanghai';
$user2->tags[] = 'new';
var_dump($user1->address->city); // Beijing(隔離)
var_dump($user1->tags); // ['vip', 'beta'](隔離)
interface Prototype {
public function __clone(): void;
public function clone(): self;
}
abstract class BasePrototype implements Prototype {
public function clone(): self {
return clone $this;
}
}
class Document extends BasePrototype {
public string $title;
public array $elements = [];
public function __clone(): void {
$this->elements = array_map(fn($el) => clone $el, $this->elements);
}
}
$template = new Document();
$template->title = 'Template';
$template->elements[] = new TextElement('Header');
$doc1 = $template->clone();
$doc1->title = 'Doc 1';
trait DeepClonable {
public function __clone(): void {
foreach ($this as $prop => $value) {
if (is_object($value)) {
$this->$prop = clone $value;
} elseif (is_array($value)) {
$this->$prop = array_map(
fn($v) => is_object($v) ? clone $v : $v,
$value
);
}
}
}
}
class ComplexObject {
use DeepClonable;
public ChildObject $child;
public array $items = [];
}
class ImmutableUser {
public function __construct(
public readonly string $id,
public readonly string $name,
public readonly Address $address // readonly 只防修改引用,不防修改對象內部
) {}
}
$user = new ImmutableUser('1', 'Alice', new Address('Beijing'));
$cloned = clone $user; // 报错:Cannot clone object with readonly properties (PHP 8.1+)
// 解決:去掉 readonly 或在 __clone 重新初始化(但 readonly 不允許修改)
// 結論:readonly 類不適合 clone,改用 wither 模式
class ImmutableUser {
public function __construct(
public readonly string $id,
public readonly string $name,
public readonly Address $address
) {}
public function withName(string $name): self {
return new self($this->id, $name, $this->address);
}
}
$user1 = new ImmutableUser('1', 'Alice', new Address('Beijing'));
$user2 = $user1->withName('Bob'); // 新實例,原實例不變
unserialize(serialize($obj)) 可深拷貝但極慢,且不支援資源/閉包,不推薦。__clone 明確深拷貝邏輯。@property 拷貝策略(淺/深),便於團隊維護。