PHP 提供九個魔術常數 (值隨程式碼位置改變) 與十七個魔術方法 (特定生命週期自動觸發)。掌握它們能大幅簡化除錯、序列化、物件行為控制等常見任務。本文分類整理、附實際輸出範例、說明常見誤區與最佳實踐。
一、九大魔術常數 (編譯時解析)
| 常數 | 說明 | 範例輸出 | 版本 |
__LINE__ | 當前行號 | 15 | 所有 |
__FILE__ | 完整絕對路徑 (已解析符號連結) | /var/www/html/index.php | 4.0.2+ |
__DIR__ | 所在目錄 (等同 dirname(__FILE__)) | /var/www/html | 5.3.0+ |
__FUNCTION__ | 函式名稱 (區分大小寫) | myFunc | 4.3.0+ |
__CLASS__ | 類別名稱 (含命名空間,區分大小寫) | App\Models\User | 4.3.0+ |
__TRAIT__ | Trait 名稱 (含命名空間) | App\Traits\Loggable | 5.4.0+ |
__METHOD__ | 類別方法名稱 (含類別名) | App\Models\User::getName | 5.0.0+ |
__NAMESPACE__ | 當前命名空間名稱 | App\Models | 5.3.0+ |
::class | 類別完全限定名 (非魔術常數,但常搭配使用) | App\Models\User | 5.5.0+ |
實際輸出範例
<?php
namespace App\Controllers;
class Demo {
public function test() {
echo "__LINE__: " . __LINE__ . "\n"; // 5
echo "__FILE__: " . __FILE__ . "\n"; // /path/to/Demo.php
echo "__DIR__: " . __DIR__ . "\n"; // /path/to
echo "__FUNCTION__: " . __FUNCTION__ . "\n"; // test
echo "__CLASS__: " . __CLASS__ . "\n"; // App\Controllers\Demo
echo "__METHOD__: " . __METHOD__ . "\n"; // App\Controllers\Demo::test
echo "__NAMESPACE__: " . __NAMESPACE__ . "\n"; // App\Controllers
}
}
(new Demo())->test();
二、十七大魔術方法 (生命週期鉤子)
1. 建構/解構/複製
| 方法 | 觸發時機 | 典型用途 | 回傳值 |
__construct(...$args) | new Class() | 初始化屬性、注入依賴 | 無 (不可回傳值) |
__destruct() | 物件被銷毀 (引用計數歸零、腳本結束) | 釋放資源、關閉連線、記錄日誌 | 無 |
__clone() | clone $obj | 深層複製屬性、重新初始化資源 ID | 無 |
2. 屬性存取攔截
| 方法 | 觸發條件 | 參數 | 典型用途 |
__get($name) | 讀取不可見屬性 (不存在、protected/private) | 屬性名 | 動態屬性、延遲載入、代理模式 |
__set($name, $value) | 寫入不可見屬性 | 屬性名、值 | 驗證、轉換、觸發事件 |
__isset($name) | isset($obj->prop) 或 empty() | 屬性名 | 定義「存在」邏輯 |
__unset($name) | unset($obj->prop) | 屬性名 | 清理相關資源 |
3. 方法呼叫攔截
| 方法 | 觸發條件 | 參數 | 典型用途 |
__call($name, $args) | 實例呼叫不可見方法 | 方法名、參數陣列 | 動態方法、委派、Fluent API |
__callStatic($name, $args) | 靜態呼叫不可見方法 | 方法名、參數陣列 | 工廠方法、查詢建構器 |
4. 字串/序列化/匯出
| 方法 | 觸發條件 | 回傳值要求 | 典型用途 |
__toString() | echo $obj, (string)$obj, printf('%s', $obj) | 必須回傳 string | 物件輸出格式化、ID 顯示 |
__invoke(...$args) | $obj() 當作函式呼叫 | 任意 | 可呼叫物件、策略模式、中介軟體 |
__set_state($props) | var_export($obj) | 回傳新物件實例 | 匯出可執行程式碼重建物件 |
__serialize() | serialize($obj) (PHP 7.4+) | 回傳關聯陣列 [prop => val] | 自訂序列化資料、排除敏感欄位 |
__unserialize($data) | unserialize($str) (PHP 7.4+) | 無 (還原 $this) | 對應 __serialize 還原 |
__sleep() | serialize($obj) (舊版) | 回傳屬性名陣列 | 選擇性序列化、關閉資源 |
__wakeup() | unserialize($str) (舊版) | 無 (重新初始化) | 重新建立連線、驗證資料完整性 |
5. 自動載入 (已棄用)
// PHP 7.2 棄用,8.0 移除,請改用 spl_autoload_register()
function __autoload($class) { /* ... */ }
三、常見誤區與最佳實踐
- __toString 必須回傳字串:回傳非字串會拋出
Error (PHP 7.4+) 或 E_RECOVERABLE_ERROR。
- __sleep 回傳陣列決定序列化哪些屬性:若省略父類屬性,父類
__sleep 不會自動被呼叫,需手動 array_merge(parent::__sleep(), [...])。
- __serialize/__unserialize 取代 __sleep/__wakeup:新版更直觀、支援類型提示、不依賴屬性可見性。
- __call/__callStatic 僅攔截「不可見」方法:public 方法不會觸發。
- __destruct 例外無法被捕捉:在解構子中避免拋出例外,改用
error_log()。
- 魔術常數在 include/require 中行為不同:
__FILE__ 為被包含檔案路徑,__DIR__ 同理。
四、實戰範例:動態屬性代理類
class Entity
{
private array $data = [];
private array $original = [];
public function __construct(array $data = []) {
$this->data = $data;
$this->original = $data;
}
public function __get(string $name) {
return $this->data[$name] ?? null;
}
public function __set(string $name, $value): void {
$this->data[$name] = $value;
}
public function __isset(string $name): bool {
return isset($this->data[$name]);
}
public function __unset(string $name): void {
unset($this->data[$name]);
}
public function isDirty(): bool {
return $this->data !== $this->original;
}
public function getChanges(): array {
return array_diff_assoc($this->data, $this->original);
}
public function __serialize(): array {
return ['data' => $this->data, 'original' => $this->original];
}
public function __unserialize(array $data): void {
$this->data = $data['data'];
$this->original = $data['original'];
}
}
// 使用
$user = new Entity(['name' => 'Alice']);
$user->email = 'alice@example.com'; // __set
echo $user->name; // __get -> Alice
serialize($user); // __serialize
魔術常數提供「程式碼感知」能力,魔術方法提供「物件行為擴展」能力。善用它們能讓框架級程式碼更優雅、業務邏輯更聚焦,但濫用會導致隱式行為難以追蹤,建議在基礎設施層 (Model 基類、DTO、Repository) 使用,業務層盡量顯式。