PHP 中以雙下劃線 __ 開頭的類方法被稱為「魔術方法」,它們在特定生命週期節點被自動調用,賦予物件「智能行為」。掌握全套魔術方法,是進階 PHP 面向對象開發的必修課。本文系統梳理 16 個魔術方法的觸發時機、參數、返回值與實戰場景。
| 方法 | 觸發場景 | 參數 | 返回值要求 | 典型用途 |
|---|---|---|---|---|
| __construct | 實例化 new Class() | 任意 | 無 | 初始化屬性、依賴注入 |
| __destruct | 物件銷毀(引用計數歸零、腳本結束) | 無 | 無 | 釋放資源、關閉連接、寫入日誌 |
| __call | 調用不可訪問的實例方法 | $name, $arguments | 任意 | 動態代理、方法轉發、流式介面 |
| __callStatic | 調用不可訪問的靜態方法 | $name, $arguments | 任意 | 靜態代理、工廠模式簡寫 |
| __get | 讀取不可訪問屬性 | $name | 任意 | 動態屬性、虛擬字段、延遲加載 |
| __set | 寫入不可訪問屬性 | $name, $value | 無 | 屬性驗證、自動轉換、觸發事件 |
| __isset | isset()/empty() 檢測不可訪問屬性 | $name | bool | 虛擬屬性存在性判斷 |
| __unset | unset() 不可訪問屬性 | $name | 無 | 清理虛擬屬性、緩存失效 |
| __toString | 物件作為字符串使用(echo、print、字符串拼接) | 無 | string | 友好輸出、日誌記錄、API 響應 |
| __invoke | 物件作為函數調用 $obj() | 任意 | 任意 | 可調用物件、閉包替代、策略模式 |
| __sleep | serialize() 序列化前 | 無 | array (屬性名列表) | 控制序列化字段、關閉資源 |
| __wakeup | unserialize() 反序列化後 | 無 | 無 | 重建連接、初始化臨時屬性 |
| __serialize | serialize() (PHP 7.4+) | 無 | array (鍵值對) | 精確控制序列化數據結構 |
| __unserialize | unserialize() (PHP 7.4+) | array $data | 無 | 精確控制反序列化邏輯 |
| __set_state | var_export() 導出類時 | array $properties | object | 自定義導出還原邏輯 |
| __clone | clone $obj 複製物件後 | 無 | 無 | 深拷貝、重置唯一標識、複製資源 |
| __debugInfo | var_dump()/print_r() 輸出物件 (PHP 5.6+) | 無 | array | 控制調試輸出、隱藏敏感字段 |
<?php
class User {
public $name;
private $db;
public $id;
public function __construct($name) {
$this->name = $name;
$this->db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$this->id = uniqid();
echo "__construct: 用戶 {$this->name} 初始化\n";
}
public function __destruct() {
$this->db = null; // 關閉連接
echo "__destruct: 用戶 {$this->name} 銷毀\n";
}
public function __clone() {
$this->id = uniqid(); // 重置唯一標識
$this->db = clone $this->db; // 深拷貝資源(若支持)
echo "__clone: 用戶 {$this->name} 複製,新 ID: {$this->id}\n";
}
}
$u1 = new User('Alice');
$u2 = clone $u1; // 觸發 __clone
unset($u1); // 觸發 __destruct
?>
<?php
class Entity {
private $data = [];
private $dirty = [];
public function __set($name, $value) {
// 自動去除首尾空格、類型轉換
$this->data[$name] = trim((string)$value);
$this->dirty[$name] = true;
echo "__set: {$name} = {$value}\n";
}
public function __get($name) {
echo "__get: 讀取 {$name}\n";
return $this->data[$name] ?? null;
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name], $this->dirty[$name]);
echo "__unset: 刪除 {$name}\n";
}
public function getDirty() { return $this->dirty; }
}
$e = new Entity();
$e->name = ' Tom '; // __set 自動 trim
echo $e->name; // __get 輸出 Tom
var_dump(isset($e->name)); // __isset true
unset($e->name); // __unset
var_dump(isset($e->name)); // __isset false
?>
<?php
class Router {
public function __call($method, $args) {
// 動態路由:$router->getUsers(['id'=>1]) -> handle('GET', 'users', ...)
$httpMethod = strtoupper(substr($method, 0, 3)); // get, post, put, del
$resource = strtolower(substr($method, 3));
return $this->handle($httpMethod, $resource, $args[0] ?? []);
}
public static function __callStatic($method, $args) {
// 靜態工廠:User::findById(1) -> find('id', 1)
$action = strtolower(substr($method, 0, 3)); // fin, get, cre
$field = strtolower(substr($method, 3));
return (new static)->{$action}($field, $args[0] ?? null);
}
private function handle($method, $resource, $params) {
return "Handling {$method} {$resource} with " . json_encode($params);
}
}
$router = new Router();
echo $router->getUsers(['id' => 1]); // __call
echo Router::findByName('Alice'); // __callStatic
?>
<?php
class User {
public $name;
private $password; // 敏感字段不序列化
private $db; // 資源不序列化
public $lastLogin;
// 舊 API (PHP < 7.4)
public function __sleep() {
return ['name', 'lastLogin']; // 只序列化這兩個
}
public function __wakeup() {
$this->db = new PDO(...); // 重建連接
$this->password = null; // 敏感字段置空
}
// 新 API (PHP 7.4+) - 更強大、類型安全、支持介面
public function __serialize(): array {
return [
'name' => $this->name,
'lastLogin' => $this->lastLogin,
];
}
public function __unserialize(array $data): void {
$this->name = $data['name'];
$this->lastLogin = $data['lastLogin'];
$this->db = new PDO(...);
$this->password = null;
}
}
$user = new User();
$user->name = 'Bob';
$user->lastLogin = time();
$s = serialize($user); // 使用新 API
$u2 = unserialize($s);
?>
<?php
class Middleware {
private $next;
public function __construct(callable $next = null) {
$this->next = $next;
}
public function __invoke(Request $req, Response $res) {
echo "Middleware 執行前\n";
if ($this->next) {
($this->next)($req, $res); // 調用下一個中間件
}
echo "Middleware 執行後\n";
}
}
// 構建中間件鏈
$chain = new Middleware(
new Middleware(
new Middleware(function($req, $res) { echo "核心處理\n"; })
)
);
$chain(new Request(), new Response()); // 像函數一樣調用
?>
<?php
class ApiToken {
public $token;
public $expiresAt;
private $secretKey = 'super-secret';
public function __debugInfo() {
return [
'token' => substr($this->token, 0, 8) . '***', // 脫敏
'expiresAt' => $this->expiresAt,
'isValid' => $this->expiresAt > time(),
// secretKey 完全隱藏
];
}
}
$t = new ApiToken();
$t->token = 'abcdefghijklmnopqrstuvwxyz';
$t->expiresAt = time() + 3600;
var_dump($t);
/*
object(ApiToken)#1 (3) {
["token"]=> string(11) "abcdefgh***"
["expiresAt"]=> int(1700000000)
["isValid"]=> bool(true)
}
*/
?>
Error(PHP 7.4+)或 E_RECOVERABLE_ERROR。clone,否則共享同一實例導致數據污染。__debugInfo、__serialize 過濾密鑰、密碼、Token 等敏感字段。16 個魔術方法覆蓋了物件生命週期的每個關鍵節點。理解其觸發機制、參數約束、返回值要求,能讓你寫出更優雅、更安全、更具表達力的物件導向代碼。**現代 PHP 開發核心建議**:優先使用 __serialize/__unserialize 替代舊序列化 API,善用 __debugInfo 保護敏感信息,謹慎使用動態重載,拥抱類型聲明與靜態分析。