什麼是魔術方法?
PHP 將所有以雙底線 __ 開頭的方法視為魔術方法,它們在特定時機自動被呼叫,實現「重載」與物件生命週期攔截。自訂方法不可使用 __ 開頭。
15 大魔術方法分類總覽
物件生命週期
| 方法 | 觸發時機 | 備註 |
__construct() | 實例化物件時 | 初始化資源、注入依賴 |
__destruct() | 物件銷毀/腳本結束 | 釋放資源、關閉連線、不可拋出異常 |
__clone() | clone $obj 完成後 | 調整複製行為、防止單例被複製 |
屬性重載 (Property Overloading)
| 方法 | 觸發條件 | 參數 |
__get($name) | 讀取不可存取/不存在屬性 | $name: 屬性名 |
__set($name, $value) | 寫入不可存取/不存在屬性 | $name, $value |
__isset($name) | isset()/empty() 檢查不可存取屬性 | $name |
__unset($name) | unset() 不可存取屬性 | $name |
限制:不可宣告為 static,PHP 5.3+ 會發出警告。
方法重載 (Method Overloading)
| 方法 | 觸發條件 | 參數 |
__call($name, $args) | 呼叫不可存取/不存在實例方法 | $name: 方法名, $args: 參數陣列 |
__callStatic($name, $args) | 靜態呼叫不可存取/不存在靜態方法 | $name, $args (PHP 5.3+) |
序列化控制
| 方法 | 觸發時機 | 回傳值/用途 |
__sleep() | serialize() 前 | 回傳需序列化屬性名陣列;回傳 null 會產生 notice |
__wakeup() | unserialize() 後 | 重建資源連線、初始化狀態 |
類型轉換與呼叫
| 方法 | 觸發時機 | 限制 |
__toString() | 物件當字串使用 (echo/print) | 必須回傳字串;不可拋出異常 (PHP 5.2+ 支援 printf %s) |
__invoke($args...) | 物件當函數呼叫 $obj() | PHP 5.3+;配合 is_callable() 判斷 |
匯出與除錯
| 方法 | 觸發時機 | 備註 |
__set_state($arr) | var_export() 匯出類別 | 靜態方法;回傳值作為匯出結果 (PHP 5.1+) |
__debugInfo() | var_dump() 輸出物件 | 回傳陣列控制顯示屬性 (PHP 5.6+) |
完整實作範例
<?php
class MagicDemo {
protected $data = [];
// 生命週期
public function __construct() {
echo __METHOD__ . " 實例化物件\n";
}
public function __destruct() {
echo __METHOD__ . " 銷毀物件\n";
}
public function __clone() {
echo __METHOD__ . " 複製物件\n";
}
// 屬性重載
public function __set($name, $value) {
$this->data[$name] = $value;
echo __METHOD__ . " 設定 {$name} = {$value}\n";
}
public function __get($name) {
echo __METHOD__ . " 讀取 {$name}\n";
return isset($this->data[$name]) ? $this->data[$name] : null;
}
public function __isset($name) {
echo __METHOD__ . " 檢查 {$name}\n";
return isset($this->data[$name]);
}
public function __unset($name) {
echo __METHOD__ . " 刪除 {$name}\n";
unset($this->data[$name]);
}
// 方法重載
public function __call($name, $args) {
echo __METHOD__ . " 呼叫方法 {$name} 參數: " . implode(', ', $args) . "\n";
return "magic method\n";
}
public static function __callStatic($name, $args) {
echo __METHOD__ . " 靜態呼叫 {$name} 參數: " . implode(', ', $args) . "\n";
return "magic static\n";
}
// 序列化
public function __sleep() {
echo __METHOD__ . " 準備序列化\n";
return array_keys($this->data);
}
public function __wakeup() {
echo __METHOD__ . " 反序列化完成\n";
}
// 類型轉換
public function __toString() {
return __CLASS__ . "\n";
}
public function __invoke($arg) {
echo __METHOD__ . " 當函數呼叫,參數: {$arg}\n";
return __METHOD__ . "\n";
}
// 匯出/除錯
public static function __set_state($arr) {
echo __METHOD__ . " var_export 匯出\n";
$obj = new self();
$obj->data = $arr;
return $obj;
}
public function __debugInfo() {
echo __METHOD__ . " var_dump 除資訊\n";
return ['data' => 'from __debugInfo'];
}
}
// 測試所有魔術方法
$obj = new MagicDemo(); // __construct
$obj->title = "hello"; // __set
echo $obj->title; // __get
$obj->test("a", "b", "c"); // __call
MagicDemo::staticMethod("x", "y"); // __callStatic
var_dump(isset($obj->aaa)); // __isset
unset($obj->aaa); // __unset
$serialized = serialize($obj); // __sleep
unserialize($serialized); // __wakeup
echo $obj; // __toString
$obj("invoke test"); // __invoke
$obj2 = clone $obj; // __clone
eval('$obj3 = ' . var_export($obj, true) . ';'); // __set_state
var_dump($obj); // __debugInfo
魔術常數補充
隨程式碼位置變化的 8 個常數:
| 常數 | 含義 |
__LINE__ | 當前行號 |
__FILE__ | 完整檔案路徑 |
__DIR__ | 檔案所在目錄 |
__FUNCTION__ | 函數名稱 |
__CLASS__ | 類別名稱 |
__TRAIT__ | Trait 名稱 |
__METHOD__ | 類別方法名 |
__NAMESPACE__ | 命名空間名稱 |
常用於除錯日誌、動態載入、錯誤追蹤。
最佳實踐建議
- 慎用重載:
__get/__set/__call 會降低程式碼可讀性與 IDE 提示支援,必要時才使用
- 序列化安全:
__sleep 排除敏感資源 (資料庫連線、檔案控制代碼)
- 單例防複製:
__clone 中拋出異常或觸發錯誤
- 除資訊控制:
__debugInfo 隱藏敏感屬性 (密碼、Token)
__destruct 中不可拋出異常,會導致 Fatal Error