PHP 魔術方法完整圖鑑:從建構子到序列化、呼叫攔截與物件呼叫的 17 個關鍵方法
魔術方法以 __ 雙底線開頭,由特定事件自動觸發,無需手動呼叫。掌握它們能解鎖 PHP 物件導向的高階能力。
生命週期三劍客
| 方法 | 觸發時機 | 典型用途 | 參數/回傳 |
__construct() | 實例化 new Class() | 依賴注入、資源初始化、驗證參數 | 任意參數,無回傳 |
__destruct() | 物件銷毀(無引用/腳本結束) | 關閉連線、釋放鎖、寫入日誌 | 無參數,無回傳 |
__clone() | clone $obj | 深層複製內部物件、重置唯一識別碼 | 無參數,無回傳 |
屬性多載(Property Overloading)
處理不可存取屬性(不存在、protected/private)的讀寫檢查:
| 方法 | 觸發操作 | 參數 | 回傳 |
__get($name) | 讀取 $obj->prop | 屬性名 | 屬性值 |
__set($name, $value) | 寫入 $obj->prop = $val | 屬性名、值 | 無 |
__isset($name) | isset($obj->prop) | 屬性名 | bool |
__unset($name) | unset($obj->prop) | 屬性名 | 無 |
實戰:動態資料容器 + 唯讀屬性
class Device {
private $data = [];
private $readonly = ['id', 'created_at'];
public function __get($name) {
return $this->data[$name] ?? null;
}
public function __set($name, $value) {
if (in_array($name, $this->readonly, true)) {
throw new LogicException("屬性 {$name} 為唯讀");
}
$this->data[$name] = $value;
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
if (in_array($name, $this->readonly, true)) return;
unset($this->data[$name]);
}
}
物件轉換與表示
| 方法 | 觸發時機 | 回傳 | 注意 |
__toString() | echo $obj、(string)$obj | string | 拋出例外會致命錯誤 |
__invoke($args...) | $obj(...) 當函數呼叫 | 任意 | PHP 5.3+,支援可呼叫物件 |
__set_state($array) | var_export($obj) | 新實例 | static、PHP 5.1+ |
__debugInfo() | var_dump($obj) | array | PHP 5.6+,控制除錯輸出 |
範例:可呼叫的服務類
class Logger {
public function __invoke($level, $message) {
$time = date('Y-m-d H:i:s');
error_log("[{$time}] [{$level}] {$message}");
}
}
$log = new Logger();
$log('INFO', 'Application started'); // 相當於 $log->__invoke(...)
序列化控制
| 方法 | 觸發 | 回傳 | 用途 |
__sleep() | serialize($obj) | array (屬性名列表) | 排除資源連線、暫存資料 |
__wakeup() | unserialize($str) | void | 重建連線、初始化暫存 |
__serialize() | serialize($obj) (PHP 7.4+) | array | 取代 __sleep,更精確 |
__unserialize() | unserialize($str) (PHP 7.4+) | void | 取代 __wakeup,配合 __serialize |
範例:排除 PDO 連線
class Repository {
private $pdo;
private $cache = [];
public function __sleep() {
return ['cache']; // 僅序列化快取
}
public function __wakeup() {
$this->pdo = new PDO(/* 連線參數 */);
}
}
方法多載(Method Overloading)
| 方法 | 觸發 | 參數 | 回傳 |
__call($name, $args) | 呼叫不可存取實例方法 | 方法名、參數陣列 | 任意 |
__callStatic($name, $args) | 呼叫不可存取靜態方法 | 方法名、參數陣列 | 任意 |
實戰:委派給內部物件
class Service {
private $driver;
public function __construct(DriverInterface $driver) {
$this->driver = $driver;
}
public function __call($method, $args) {
if (method_exists($this->driver, $method)) {
return $this->driver->$method(...$args);
}
throw new BadMethodCallException("Method {$method} not found");
}
}
已棄用:__autoload()
PHP 7.2 棄用、8.0 移除。請改用 spl_autoload_register() 或 Composer PSR-4 自動載入。
速查總表
| 分類 | 方法 | PHP 版本 |
| 生命週期 | __construct | 5.0 |
| __destruct | 5.0 |
| __clone | 5.0 |
| 屬性多載 | __get | 5.0 |
| __set | 5.0 |
| __isset | 5.0 |
| __unset | 5.0 |
| 轉換/表示 | __toString | 5.0 |
| __invoke | 5.3 |
| __set_state | 5.1 |
| __debugInfo | 5.6 |
| __serialize | 7.4 |
| 序列化 | __sleep | 5.0 |
| __wakeup | 5.0 |
| 方法多載 | __call | 5.0 |
| __callStatic | 5.3 |
善用魔術方法能編寫出極具表達力的 DSL、動態 Proxy、不可變值物件等進階模式,但濫用會降低程式碼可讀性,請在架構層級謹慎決策。