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

PHP 魔術方法完整指南:從 __construct 到 __invoke 的 11 大核心用法

     編輯:PHP 相關知識

什麼是魔術方法?

魔術方法是以雙底線 __ 開頭的特殊方法,PHP 在特定生命週期或語法情境下自動觸發。它們屬於「語法糖」,不新增語言功能,但大幅提升程式碼表達力與靈活度。

11 大魔術方法速覽表

方法觸發時機典型用途
__construct物件實例化 new Class()初始化屬性、注入依賴、建立連線
__destruct物件銷毀(腳本結束、unset、引用歸零)釋放資源、關閉連線、寫入日誌
__call呼叫不可存取的實例方法動態代理、方法不存在時的 fallback、DSL 建構
__callStatic呼叫不可存取的靜態方法靜態代理、流式介面靜態入口
__get讀取不可存取的屬性虛擬屬性、資料轉換、懶惰載入
__set寫入不可存取的屬性驗證、記錄變更、關聯更新
__isset對不可存取屬性執行 isset()/empty()配合 __get 實作虛擬屬性存在性判斷
__unset對不可存取屬性執行 unset()清理虛擬屬性、觸發事件
__sleepserialize() 前指定需序列化的屬性、關閉資源連線
__wakeupunserialize() 後重建資源連線、初始化狀態
__toString物件被當作字串使用(echo、print、字串串接)物件友善輸出、日誌記錄
__invoke物件被當作函式呼叫 $obj()可呼叫物件、策略模式、中介軟體
__set_statevar_export() 匯出類別時自定義匯出陣列結構,支援 eval() 還原
__cloneclone $obj 時深層複製、重設識別碼、複製相關資源
__debugInfovar_dump() 顯示物件時控制除錯輸出內容,隱藏敏感資訊

核心範例深度解析

1. 建構/解構與資源管理

class Database {
    private $pdo;
    public function __construct(string $dsn, string $user, string $pass) {
        $this->pdo = new PDO($dsn, $user, $pass);
    }
    public function __destruct() {
        $this->pdo = null; // 關閉連線
    }
}

2. 屬性過載(__get/__set/__isset/__unset)

class Entity {
    private array $data = [];
    private array $changed = [];

    public function __get(string $name) {
        return $this->data[$name] ?? null;
    }
    public function __set(string $name, $value): void {
        $this->data[$name] = $value;
        $this->changed[$name] = true;
    }
    public function __isset(string $name): bool {
        return isset($this->data[$name]);
    }
    public function __unset(string $name): void {
        unset($this->data[$name], $this->changed[$name]);
    }
    public function getDirty(): array { return array_keys($this->changed); }
}

$e = new Entity();
$e->title = 'Hello';        // 觸發 __set
echo $e->title;             // 觸發 __get
var_dump(isset($e->title)); // 觸發 __isset
unset($e->title);           // 觸發 __unset

3. 方法過載(__call/__callStatic)

class Router {
    public function __call(string $method, array $args) {
        // 動態處理 getUsers、postLogin 等
        $verb = strtolower(substr($method, 0, 3)); // get/post/put/delete
        $action = lcfirst(substr($method, 3));
        echo "Route: $verb $action" . PHP_EOL;
    }
    public static function __callStatic(string $method, array $args) {
        echo "Static route: $method" . PHP_EOL;
    }
}
$r = new Router();
$r->getUsers();        // Route: get users
Router::dispatch();    // Static route: dispatch

4. 序列化控制(__sleep/__wakeup)

class UserSession {
    public $userId;
    public $token;
    private $pdo; // 資料庫連線不可序列化

    public function __sleep(): array {
        return ['userId', 'token']; // 僅序列化這兩個屬性
    }
    public function __wakeup(): void {
        $this->pdo = new PDO('mysql:host=localhost;dbname=app', 'user', 'pass');
    }
}

5. 字串轉換(__toString)

class Money {
    public function __construct(public float $amount, public string $currency = 'TWD') {}
    public function __toString(): string {
        return number_format($this->amount, 2) . ' ' . $this->currency;
    }
}
$price = new Money(1299.5);
echo "價格:$price"; // 價格:1,299.50 TWD

6. 可呼叫物件(__invoke)

class Logger {
    public function __invoke(string $level, string $message): void {
        echo "[$level] " . date('Y-m-d H:i:s') . " $message" . PHP_EOL;
    }
}
$log = new Logger();
$log('INFO', 'Application started'); // [INFO] 2024-01-15 10:30:00 Application started

7. 深層複製(__clone)

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

8. 除錯資訊控制(__debugInfo)

class ApiKey {
    public function __construct(public string $key) {}
    public function __debugInfo(): array {
        return ['key' => '****' . substr($this->key, -4)]; // 隱藏大部分金鑰
    }
}
var_dump(new ApiKey('sk_live_abcdef123456'));
// object(ApiKey)#1 (1) { ["key"]=> string(10) "****123456" }

自動載入:__autoload vs spl_autoload_register

舊式:全域函式 __autoload($class),只能定義一個,已棄用。
新式:spl_autoload_register(callable $callback) 可註冊多個載入器,支援類別靜態方法、閉包、多命名空間對應。

spl_autoload_register(function(string $class) {
    $file = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php';
    if (file_exists($file)) require $file;
});

最佳實踐與避坑

  • 回傳型別:__toString 必須回傳 string,否則拋出 Error。
  • 可見性:所有魔術方法必須宣告為 public,__callStatic 亦需 static。
  • __autoload 例外:在 __autoload 拋出的例外無法被 catch 捕獲,會導致致命錯誤;改用 spl_autoload_register 並內部 try-catch。
  • 序列化效能:大型物件建議實作 __sleep 排除不需持久化的資料,減少體積。
  • PHP 8.1+:__serialize() / __unserialize() 取代 __sleep/__wakeup,提供更細粒度控制。

結語

魔術方法是 PHP 物件模型的靈魂,善用它們能實作 ActiveRecord、資料傳輸物件(DTO)、中介軟體、流式介面等優雅模式。但濫用會降低可讀性,建議:「核心領域模型慎用,基礎設施層大膽用」。

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