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

PHP 5 魔術方法完整解析與實戰應用指南

     編輯:PHP 綜合知識

魔術方法總覽

PHP 5 起提供以 __ 雙底線開頭的魔術方法,在特定生命週期自動觸發。建議自訂方法避免雙底線開頭,防止衝突。

方法觸發時機參數回傳值要求
__construct物件實例化時任意無
__destruct物件銷毀前 (GC/腳本結束)無無
__call呼叫不可存取/不存在的實例方法$method, $args任意
__callStatic呼叫不可存取/不存在的靜態方法 (PHP 5.3+)$method, $args任意
__get讀取不可存取/不存在的屬性$name任意
__set寫入不可存取/不存在的屬性$name, $value無
__isset對不可存取屬性呼叫 isset()/empty()$namebool
__unset對不可存取屬性呼叫 unset()$name無
__sleepserialize() 前無array (需序列化的屬性名)
__wakeupunserialize() 後無無
__toString物件當作字串使用 (echo/print/字串串接)無string
__invoke物件當作函數呼叫 $obj() (PHP 5.3+)任意任意
__set_statevar_export() 匯出類別時 (PHP 5.1+)$properties物件實例
__cloneclone 複製物件後無無
__debugInfovar_dump() 輸出物件時 (PHP 5.6+)無array

重點方法深度解析

1. __get / __set / __isset / __unset —— 屬性攔截

class Entity {
    private $data = [];
    public function __get($name) {
        return $this->data[$name] ?? null;
    }
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
    public function __isset($name) {
        return isset($this->data[$name]);
    }
    public function __unset($name) {
        unset($this->data[$name]);
    }
}
$e = new Entity();
$e->name = 'Tom';      // 觸發 __set
echo $e->name;         // 觸發 __get
isset($e->name);       // 觸發 __isset
unset($e->name);       // 觸發 __unset

注意:__get 回傳值不會被引用,$e->arr[] = 1 會失效,需改用 $tmp = $e->arr; $tmp[] = 1; $e->arr = $tmp; 或實作 ArrayAccess。

2. __call / __callStatic —— 方法攔截與動態代理

class Service {
    public function __call($method, $args) {
        if (strpos($method, 'get') === 0) {
            $field = lcfirst(substr($method, 3));
            return $this->fetch($field);
        }
        throw new BadMethodCallException("Method $method not found");
    }
    static public function __callStatic($method, $args) {
        // 靜態代理邏輯
    }
    private function fetch($field) { return "data:$field"; }
}
$s = new Service();
echo $s->getName(); // 輸出 "data:name"

3. __sleep / __wakeup —— 序列化控制

class DbConnection {
    protected $link;
    protected $dsn, $user, $pass;
    public function __construct($dsn, $user, $pass) {
        $this->dsn = $dsn; $this->user = $user; $this->pass = $pass;
        $this->connect();
    }
    public function __sleep() {
        return ['dsn', 'user', 'pass']; // 不序列化 $link (resource 不可序列化)
    }
    public function __wakeup() {
        $this->connect(); // 反序列化後自動重連
    }
    private function connect() { /* ... */ }
}

4. __toString —— 物件字串化

class Money {
    private $amount, $currency;
    public function __construct($amount, $currency) { $this->amount = $amount; $this->currency = $currency; }
    public function __toString() {
        return number_format($this->amount, 2) . ' ' . $this->currency;
    }
}
$price = new Money(1999.99, 'USD');
echo $price; // "1,999.99 USD"
echo "Price: $price"; // 字串插值也觸發

PHP 5.2+ 任何字串語境 (printf %s) 皆觸發;未實作則拋出 E_RECOVERABLE_ERROR。

5. __invoke —— 可呼叫物件

class Multiplier {
    private $factor;
    public function __construct($factor) { $this->factor = $factor; }
    public function __invoke($x) { return $x * $this->factor; }
}
$double = new Multiplier(2);
echo $double(5); // 10
// 可作為 callable 傳遞
array_map(new Multiplier(3), [1,2,3]); // [3,6,9]

6. __clone —— 深層複製控制

class Order {
    public $id;
    public $items; // 陣列物件
    public function __clone() {
        $this->id = uniqid(); // 新訂單號
        $this->items = array_map(fn($item) => clone $item, $this->items); // 深層複製商品
    }
}
$newOrder = clone $oldOrder;

7. __autoload / spl_autoload_register —— 自動載入 (已棄用/取代)

PHP 7.2 棄用 __autoload,PHP 8.0 移除。 請一律使用 spl_autoload_register() 並配合 Composer PSR-4 自動載入。

// 現代做法:composer.json 定義 autoload,無需手寫
// 手動註冊範例 (不建議)
spl_autoload_register(function ($class) {
    $file = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php';
    if (file_exists($file)) require $file;
});

8. __set_state —— var_export 還原

class Config {
    public $settings = [];
    public static function __set_state($arr) {
        $obj = new self();
        $obj->settings = $arr['settings'];
        return $obj;
    }
}
$config = new Config(); $config->settings = ['debug' => true];
eval('$c = ' . var_export($config, true) . ';'); // $c 為新 Config 實例

最佳實踐總結

  1. 單一職責:魔術方法隱含行為,文件化清楚說明觸發條件與副作用。
  2. 效能考量:__call/__get 等攔截器有微小開銷,高頻路徑優先定義明確方法/屬性。
  3. 避免濫用:能用介面 (ArrayAccess, Iterator, Serializable, Countable) 解決的優先用介面,語意更明確。
  4. 例外處理:__autoload 中拋出的例外無法被 catch (PHP 7 前),現代自動載入器內部應自行 try-catch 並轉為錯誤日誌。
  5. 測試覆蓋:魔術方法行為隱性,務必撰寫單元測試驗證序列化、複製、字串化、動態呼叫等場景。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved