首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 複製構造函數與賦值運算符重載深度解析:淺拷貝、深拷貝、__clone 魔術方法

     編輯:PHP 相關知識

前言

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 也被影響!

深拷貝:實現 __clone()

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'](隔離)

__clone 進階模式

1. 原型模式工廠

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';

2. 遞歸深拷貝通用 Trait

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 = [];
}

PHP 8.1+ readonly 屬性對拷貝的影響

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 模式

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)) 可深拷貝但極慢,且不支援資源/閉包,不推薦。
  • 大數據傳輸優先不可變值對象 + wither,避免拷貝開銷。

最佳實踐清單

  1. 類包含對象/資源屬性時,必須實現 __clone 明確深拷貝邏輯。
  2. 優先設計為不可變對象(readonly + wither),從根源消除拷貝需求。
  3. 若必須拷貝大對象圖,考慮序列化/反序列化或手工構建新實例,避免遞歸 clone 爆棧。
  4. 單元測試驗證拷貝隔離性:修改克隆對象,斷言原對象不變。
  5. 文檔註釋標註 @property 拷貝策略(淺/深),便於團隊維護。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved