PHP 魔術方法以雙底線 `__` 開頭,在物件生命週期的關鍵節點自動被觸發,是面向物件進階開發的核心工具。本文系統整理 13 個魔術方法,附上觸發時機、典型用途與程式碼片段,助你快速掌握物件行為控制。
| 方法 | 觸發時機 | 典型應用場景 |
|---|---|---|
__construct() | 實例化物件時(`new Class()`) | 初始化屬性、注入依賴、建立資源連線 |
__destruct() | 物件被銷毀或腳本結束 | 關閉資料庫連線、釋放鎖、寫入日誌 |
__call($name, $args) | 呼叫不可存取的方法(不存在或權限不足) | 動態代理、方法重載、API 客戶端魔術呼叫 |
__callStatic($name, $args) | 靜態呼叫不可存取的方法 | 靜態工廠、ActiveRecord 查詢代理 |
__get($name) | 讀取不可存取的屬性 | 資料映射、延遲載入、虛擬屬性 |
__set($name, $value) | 寫入不可存取的屬性 | 屬性驗證、自動轉型、觸發髒標記 |
__isset($name) | 對不可存取屬性執行 `isset()`/`empty()` | 配合 `__get` 實現完整屬性存取語意 |
__unset($name) | 對不可存取屬性執行 `unset()` | 清除快取、重置虛擬屬性 |
__toString() | 物件被當作字串使用(`echo $obj`) | 輸出物件摘要、JSON 序列化、除錯顯示 |
__invoke($args...) | 物件被當作函式呼叫(`$obj()`) | 建立可呼叫物件、策略模式、中間件 |
__clone() | 執行 `clone $obj` 時 | 深拷貝資源、重新生成唯一 ID、斷開共享引用 |
__sleep() | `serialize($obj)` 前 | 返回需序列化的屬性陣列、關閉資源、清理暫存 |
__wakeup() | `unserialize($data)` 後 | 重建資源連線、初始化非序列化屬性 |
__set_state($arr) | `var_export()` 導出類時 | 自定義導出格式、單例模式還原 |
__debugInfo() | `var_dump($obj)` 時 | 控制除錯輸出內容、隱藏敏感資訊 |
註:`__autoload()` 已於 PHP 7.2 棄用、PHP 8.0 移除,請改用 `spl_autoload_register()` 實現自動載入。
<?php
class Demo {
public function __construct() { echo "__construct\n"; }
public function Demo() { echo "同名建構子\n"; }
}
new Demo(); // 輸出:__construct
?>
同時存在時優先呼叫 `__construct`,舊式同名建構子不再被呼叫(PHP 7.0+ 已棄用)。
<?php
class Router {
private $routes = [];
public function __call($method, $args) {
if (strpos($method, 'get') === 0) {
$route = lcfirst(substr($method, 3));
return $this->routes[$route] ?? "404: $route";
}
throw new BadMethodCallException("Method $method not found");
}
}
$r = new Router();
$r->routes['user'] = 'User Controller';
echo $r->getUser(); // User Controller
echo $r->getOrder(); // 404: order
?>
<?php
class Entity {
private $data = [];
private $dirty = [];
public function __get($name) {
return $this->data[$name] ?? null;
}
public function __set($name, $value) {
$this->data[$name] = $value;
$this->dirty[$name] = true;
}
public function getDirty() { return $this->dirty; }
}
$e = new Entity();
$e->name = 'Tom'; // 觸發 __set
echo $e->name; // 觸發 __get
print_r($e->getDirty()); // ['name' => true]
?>
<?php
class Profile {
public $tags = [];
}
class User {
public $name;
public $profile;
public function __construct($name) {
$this->name = $name;
$this->profile = new Profile();
}
public function __clone() {
$this->profile = clone $this->profile; // 深拷貝關聯物件
$this->name .= '_copy';
}
}
$u1 = new User('Alice');
$u1->profile->tags = ['vip'];
$u2 = clone $u1;
$u2->profile->tags[] = 'admin';
print_r($u1->profile->tags); // ['vip'] 未受影響
?>
<?php
class Service {
private $pdo; // 資源無法序列化
private $config = ['host' => 'localhost'];
public function __sleep() {
return ['config']; // 僅序列化 config
}
public function __wakeup() {
$this->pdo = new PDO("mysql:host={$this->config['host']}"); // 重建連線
}
}
?>
<?php
class Multiplier {
private $factor;
public function __construct($factor) { $this->factor = $factor; }
public function __invoke($value) { return $value * $this->factor; }
}
$double = new Multiplier(2);
echo $double(5); // 10
// 可直接作為 callback
$arr = [1,2,3];
print_r(array_map($double, $arr)); // [2,4,6]
?>
<?php
class ApiKey {
public $key = 'sk_live_abc123';
public $createdAt = '2024-01-01';
public function __debugInfo() {
return [
'key' => 'sk_live_****' . substr($this->key, -4),
'createdAt' => $this->createdAt,
];
}
}
var_dump(new ApiKey());
// 輸出隱藏完整金鑰,避免日誌洩露
?>
生命週期順序: new Class() → __construct clone $obj → __clone serialize() → __sleep → 儲存字串 unserialize() → 分配記憶體 → __wakeup $obj() → __invoke echo $obj → __toString isset($obj->prop) → __isset unset($obj->prop) → __unset 腳本結束/引用歸零 → __destruct