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

深入探秘 PHP 魔術方法:從構造到序列化的完整圖解

     編輯:PHP 相關知識

前言

PHP 魔術方法以雙底線 __ 開頭,在特定生命週期或語言結構觸發時自動被呼叫,是物件導向進階開發的關鍵。本文以兩個範例類別 Device、Battery 為基礎,系統介紹 12 個常用魔術方法的觸發時機、參數、回傳值與實戰應用場景。

範例基礎類別

<?php
// Device.class.php
class Device {
    public $name;
    public $battery;
    public $data = [];
    public $connection;

    protected function connect() {
        $this->connection = 'resource';
        echo $this->name . ' connected' . PHP_EOL;
    }
    protected function disconnect() {
        $this->connection = null;
        echo $this->name . ' disconnected' . PHP_EOL;
    }
}

// Battery.class.php
class Battery {
    private $charge = 0;
    public function setCharge($charge) {
        $charge = (int)$charge;
        $this->charge = max(0, min(100, $charge));
    }
}

一、構造與解構

__construct() - 物件建立時自動呼叫

public function __construct($name, Battery $battery) {
    $this->name = $name;
    $this->battery = $battery; // 型別提示強制傳入 Battery 實例
    $this->connect();
}
// 使用
$battery = new Battery();
$device = new Device('iPhone', $battery);
  • 初始化屬性、建立連線、載入設定檔等前置作業。
  • 宣告為 private 可實作單例模式(防止外部 new)。

__destruct() - 物件銷毀前自動呼叫

public function __destruct() {
    $this->disconnect(); // 釋放資源、關閉連線、寫入日誌
}
  • 腳本結束、unset($obj)、引用計數歸零時觸發。
  • 不可拋出例外(會導致 Fatal Error),也不可依賴執行順序(循環引用時不確定)。

二、屬性多載

當存取不可見(不存在、private/protected、靜態)屬性時觸發。

__get($name) - 讀取不可見屬性

public function __get($name) {
    if (array_key_exists($name, $this->data)) {
        return $this->data[$name];
    }
    throw new Exception("屬性 {$name} 不存在");
}

__set($name, $value) - 寫入不可見屬性

public function __set($name, $value) {
    $this->data[$name] = $value; // 實作動態屬性袋
}

__isset($name) - isset()/empty() 檢查不可見屬性

public function __isset($name) {
    return isset($this->data[$name]);
}

__unset($name) - unset() 不可見屬性

public function __unset($name) {
    unset($this->data[$name]);
}

三、物件轉字串與複製

__toString() - 物件當字串使用時

public function __toString() {
    return "Device: {$this->name} [Battery: {$this->battery->getCharge()}%]";
}
// 觸發
echo $device; // 自動呼叫 __toString()
echo "Status: " . $device; // 串接字串
// 若未定義會發生 Catchable fatal error

__clone() - clone 關鍵字複製物件時

public function __clone() {
    // 深複製 battery 物件,避免共用同一實例
    $this->battery = clone $this->battery;
    $this->connection = null; // 新物件需重新連線
    $this->connect();
}
// 使用
$device2 = clone $device;

四、序列化與反序列化

__sleep() - serialize() 前呼叫

public function __sleep() {
    // 回傳需序列化的屬性名稱陣列(排除 resource、連線等)
    return ['name', 'battery', 'data'];
}

__wakeup() - unserialize() 後呼叫

public function __wakeup() {
    // 重新建立連線、初始化資源
    $this->connect();
}

五、方法多載

__call($name, $arguments) - 呼叫不可見實例方法

public function __call($name, $arguments) {
    if ($name === 'getData') {
        return $this->data[$arguments[0]] ?? null;
    }
    throw new BadMethodCallException("方法 {$name} 不存在");
}
// 觸發
$device->getData('key'); // 實際不存在 getData 方法

__callStatic($name, $arguments) - 呼叫不可見靜態方法 (PHP 5.3+)

public static function __callStatic($name, $arguments) {
    if ($name === 'create') {
        return new static(...$arguments); // 工廠方法
    }
    throw new BadMethodCallException("靜態方法 {$name} 不存在");
}
// 觸發
Device::create('iPad', new Battery());

六、物件作為函式呼叫

__invoke(...$args) - 物件當函式呼叫 (PHP 5.3+)

public function __invoke($action, ...$params) {
    if ($action === 'charge') {
        $this->battery->setCharge($params[0]);
    }
}
// 觸發
$device('charge', 80); // 如同呼叫 $device->__invoke('charge', 80)

七、自動載入類別 (已棄用,PHP 7.2+ 移除)

__autoload($className) - 實例化找不到類別時

function __autoload($className) {
    $file = __DIR__ . '/' . $className . '.class.php';
    if (file_exists($file)) require_once $file;
}
// 現代替代方案:spl_autoload_register() 或 Composer PSR-4 自動載入

總結表

魔術方法觸發時機典型用途
__constructnew 實例化初始化、依賴注入
__destruct物件銷毀資源釋放、關閉連線
__get/__set/__isset/__unset存取不可見屬性動態屬性、代理、資料映射
__toStringecho/print/串接字串物件字串化表示
__cloneclone 關鍵字深複製、重置資源
__sleep/__wakeupserialize/unserialize控制序列化內容、恢復連線
__call/__callStatic呼叫不可見方法動態代理、流暢介面、工廠
__invoke物件當函式呼叫可呼叫物件、策略模式
__autoload類別找不到舊專案相容(已棄用)

最佳實踐建議

  1. 慎用魔術方法:增加隱性行為,降低程式碼可讀性;必要時才實作。
  2. 文件化:在類別 PHPDoc 中以 @property、@method 標註動態屬性/方法,IDE 才能提示。
  3. 效能:__get/__set/__call 比直接存取慢,高頻路徑避免使用。
  4. 序列化安全:__wakeup 中驗證資料完整性,防止反序列化攻擊(CVE-2016-7124 等)。
  5. 替代方案:優先考慮介面、繼承、組合、Traits 解決問題,魔術方法作為最後手段。

結語

魔術方法是 PHP 物件模型的強大擴充點,掌握其觸發機制與限制,能讓你設計出優雅的 DSL、動態代理、資源管理等進階架構。但請記住:明確優於隱性,除非確有需要,否則保持類別簡單、可預測。

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