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

PHP 魔術方法完整解析:__construct、__toString、__call、__get、__set、__clone 實戰範例

     編輯:PHP 綜合知識

什麼是魔術方法?

PHP 中以雙底線 __ 開頭的方法稱為魔術方法,它們在特定情況下會被自動觸發,無需手動呼叫。掌握這些方法能讓類別行為更直觀、API 更優雅。

核心魔術方法速查表

方法觸發時機典型用途
__construct()實例化 new Class()初始化屬性、注入依賴、建立連線
__destruct()物件銷毀 (引用計數歸零、腳本結束)釋放資源、關閉連線、寫入日誌
__call($name, $args)呼叫不可存取/不存在的實例方法方法代理、動態方法、流暢介面
__callStatic($name, $args)呼叫不可存取/不存在的靜態方法靜態工廠、動態查詢建構器
__get($name)讀取不可存取/不存在的屬性虛擬屬性、延遲載入、委派存取
__set($name, $value)寫入不可存取/不存在的屬性屬性驗證、觸發事件、同步快取
__isset($name)對不可存取屬性呼叫 isset()/empty()虛擬屬性存在性檢查
__unset($name)對不可存取屬性呼叫 unset()清理虛擬屬性
__toString()物件被當作字串使用 (echo, print, 字串串接)物件序列化顯示、除錯輸出
__invoke()物件被當作函式呼叫 $obj()可呼叫物件、閉包替代、策略模式
__clone()執行 clone $obj深層複製、重置識別碼、複製相關資源
__sleep()serialize($obj)指定序列化屬性、關閉資源
__wakeup()unserialize($data)重建連線、初始化資源
__serialize()serialize($obj) (PHP 7.4+)自定義序列化資料陣列
__unserialize()unserialize($data) (PHP 7.4+)自定義反序列化邏輯
__debugInfo()var_dump($obj)自訂除錯輸出內容

完整實戰範例

<?php
class Person {
    public string $name;
    protected string $sex;
    private float $salary;

    // 1. 建構子
    public function __construct(string $name, string $sex, float $salary) {
        $this->name   = $name;
        $this->sex    = $sex;
        $this->salary = $salary;
        echo "建構子執行: $name 入職\n";
    }

    // 2. 解構子
    public function __destruct() {
        echo "解構子執行: $this->name 离職\n";
    }

    // 3. 字串轉換
    public function __toString(): string {
        return "Person[名字:$this->name, 性別:$this->sex]";
    }

    // 4. 複製
    public function __clone() {
        echo "__clone 觸發: 建立 $this->name 的副本\n";
        $this->name .= '_copy'; // 副本標記
        $this->salary = 0;      // 重置薪資
    }

    // 5. 讀取不可存取屬性
    public function __get(string $name) {
        echo "__get 呼叫: 嘗試讀取 '$name'\n";
        if ($name === 'salary') {
            return '***** (隱私)';
        }
        return "屬性 '$name' 不存在或無權限";
    }

    // 6. 寫入不可存取屬性
    public function __set(string $name, $value) {
        echo "__set 呼叫: 嘗試設定 '$name' = '$value'\n";
        if ($name === 'salary') {
            throw new Exception('薪資不可直接修改,請用 adjustSalary()');
        }
        $this->$name = $value; // 動態建立公開屬性
    }

    // 7. 檢查不可存取屬性是否存在
    public function __isset(string $name): bool {
        echo "__isset 呼叫: 檢查 '$name'\n";
        return in_array($name, ['name', 'sex', 'salary'], true);
    }

    // 8. 呼叫不存在方法
    public function __call(string $name, array $args) {
        echo "__call 呼叫: 方法 '$name' 不存在,參數: " . json_encode($args) . "\n";
        if ($name === 'getName') {
            return $this->name;
        }
        throw new BadMethodCallException("方法 $name 不存在");
    }

    // 9. 靜態方法代理
    public static function __callStatic(string $name, array $args) {
        echo "__callStatic 呼叫: 靜態方法 '$name'\n";
        if ($name === 'create') {
            return new self(...$args);
        }
        throw new BadMethodCallException("靜態方法 $name 不存在");
    }

    // 10. 可呼叫物件
    public function __invoke(string $greeting = 'Hello') {
        return "$greeting, $this->name!";
    }

    // 11. 序列化控制 (PHP 7.4+ 推薦用 __serialize)
    public function __sleep(): array {
        return ['name', 'sex']; // 不序列化 salary
    }

    public function __wakeup() {
        $this->salary = 0; // 反序列化後重置
    }

    // 12. 除錯資訊
    public function __debugInfo(): array {
        return [
            'name'   => $this->name,
            'sex'    => $this->sex,
            'salary' => '******',
        ];
    }

    // 一般方法
    public function adjustSalary(float $amount): void {
        $this->salary += $amount;
    }
}

// --- 測試程式碼 ---
echo "=== 1. 實例化 ===\n";
$p = new Person('張三', '男', 50000);

echo "\n=== 2. 字串轉換 ===\n";
echo $p; // 自動呼叫 __toString()

echo "\n=== 3. 複製 ===\n";
$p2 = clone $p;
echo "複製後名字: $p2->name\n";

echo "\n=== 4. 存取私有屬性 ===\n";
echo "薪資: " . $p->salary . "\n"; // 觸發 __get

echo "\n=== 5. 設定不存在屬性 ===\n";
$p->department = 'RD'; // 觸發 __set,動態建立 public 屬性
echo "部門: $p->department\n";

echo "\n=== 6. 檢查屬性存在 ===\n";
var_dump(isset($p->salary)); // 觸發 __isset

echo "\n=== 7. 呼叫不存在方法 ===\n";
echo "名字: " . $p->getName() . "\n"; // 觸發 __call

echo "\n=== 8. 靜態工廠 ===\n";
$p3 = Person::create('李四', '女', 60000); // 觸發 __callStatic

echo "\n=== 9. 可呼叫物件 ===\n";
echo $p('早安') . "\n"; // 觸發 __invoke

echo "\n=== 10. 序列化 ===\n";
$ser = serialize($p);
echo "序列化: $ser\n";
$p4 = unserialize($ser);
echo "反序列化後薪資: " . $p4->salary . "\n";

echo "\n=== 11. 除錯輸出 ===\n";
var_dump($p);

執行輸出重點解析

=== 1. 實例化 ===
建構子執行: 張三 入職

=== 2. 字串轉換 ===
Person[名字:張三, 性別:男]

=== 3. 複製 ===
__clone 觸發: 建立 張三 的副本
複製後名字: 張三_copy

=== 4. 存取私有屬性 ===
__get 呼叫: 嘗試讀取 'salary'
薪資: ***** (隱私)

=== 5. 設定不存在屬性 ===
__set 呼叫: 嘗試設定 'department' = 'RD'
部門: RD

=== 6. 檢查屬性存在 ===
__isset 呼叫: 檢查 'salary'
bool(true)

=== 7. 呼叫不存在方法 ===
__call 呼叫: 方法 'getName' 不存在,參數: []
名字: 張三

=== 8. 靜態工廠 ===
__callStatic 呼叫: 靜態方法 'create'
建構子執行: 李四 入職

=== 9. 可呼叫物件 ===
早安, 張三!

=== 10. 序列化 ===
序列化: O:6:"Person":2:{s:4:"name";s:6:"張三";s:3:"sex";s:3:"男";}
反序列化後薪資: 0

=== 11. 除錯輸出 ===
object(Person)#1 (3) {
  ["name"]=> string(6) "張三"
  ["sex"]=> string(3) "男"
  ["salary"]=> string(6) "******"
}

最佳實務與避坑指南

  1. __get/__set 效能:每次存取都經過魔術方法,頻繁存取熱點屬性建議改為公開屬性或實作具體 getter/setter。
  2. __call 避免無限遞迴:在 __call 內部呼叫不存在方法會再次觸發 __call,需檢查方法名或使用 method_exists。
  3. __clone 深層複製:預設淺複製 (引用同一物件);若屬性含物件/資源,必須在 __clone 手動 clone $this->obj。
  4. __sleep 返回陣列:必須回傳屬性名稱陣列,否則序列化失敗;私有屬性需包含類別名 '\0ClassName\0prop'。
  5. __debugInfo 安全性:避免在 var_dump 輸出敏感資料 (密碼、Token、金鑰)。
  6. 介面契約:實作 Stringable (PHP 8.0+) 介面明確宣告支援 __toString:class Person implements Stringable。

實際專案應用場景

  • ORM Model:__get/__set 實現屬性對應資料庫欄位、自動型別轉換、髒資料追蹤。
  • 集合類別:__call 代理陣列方法 (map, filter, each),__invoke 直接迭代。
  • 設定物件:__get 支援點號路徑 $config->get('database.host')。
  • 延遲載入 Proxy:__get 首次存取時才從資料庫/快取載入關聯資料。
  • 流暢介面 Builder:__call 將未定義方法轉為設定選項 $query->whereName('John')->orderBy('id')。

魔術方法是 PHP 物件導向進階開發的核心工具,善用它們能寫出極具表達力、符合直覺的 API,但也需謹慎平衡「魔法」與「明確」的界線。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved