PHP 魔術方法以雙底線 __ 開頭,在特定生命週期或語言結構觸發時自動被呼叫,是物件導向進階開發的關鍵。本文以兩個範例類別 Device、Battery 為基礎,系統介紹 12 個常用魔術方法的觸發時機、參數、回傳值與實戰應用場景。
<?php
// Device.class.php
class Device {
public $name;
public $battery;
public $data = [];
public $connection;
protected function connect() {
$this->connection = 'resource';
echo $this->name . ' connected' . PHP_EOL;
}
protected function disconnect() {
$this->connection = null;
echo $this->name . ' disconnected' . PHP_EOL;
}
}
// Battery.class.php
class Battery {
private $charge = 0;
public function setCharge($charge) {
$charge = (int)$charge;
$this->charge = max(0, min(100, $charge));
}
}
public function __construct($name, Battery $battery) {
$this->name = $name;
$this->battery = $battery; // 型別提示強制傳入 Battery 實例
$this->connect();
}
// 使用
$battery = new Battery();
$device = new Device('iPhone', $battery);
private 可實作單例模式(防止外部 new)。public function __destruct() {
$this->disconnect(); // 釋放資源、關閉連線、寫入日誌
}
unset($obj)、引用計數歸零時觸發。當存取不可見(不存在、private/protected、靜態)屬性時觸發。
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
throw new Exception("屬性 {$name} 不存在");
}
public function __set($name, $value) {
$this->data[$name] = $value; // 實作動態屬性袋
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name]);
}
public function __toString() {
return "Device: {$this->name} [Battery: {$this->battery->getCharge()}%]";
}
// 觸發
echo $device; // 自動呼叫 __toString()
echo "Status: " . $device; // 串接字串
// 若未定義會發生 Catchable fatal error
public function __clone() {
// 深複製 battery 物件,避免共用同一實例
$this->battery = clone $this->battery;
$this->connection = null; // 新物件需重新連線
$this->connect();
}
// 使用
$device2 = clone $device;
public function __sleep() {
// 回傳需序列化的屬性名稱陣列(排除 resource、連線等)
return ['name', 'battery', 'data'];
}
public function __wakeup() {
// 重新建立連線、初始化資源
$this->connect();
}
public function __call($name, $arguments) {
if ($name === 'getData') {
return $this->data[$arguments[0]] ?? null;
}
throw new BadMethodCallException("方法 {$name} 不存在");
}
// 觸發
$device->getData('key'); // 實際不存在 getData 方法
public static function __callStatic($name, $arguments) {
if ($name === 'create') {
return new static(...$arguments); // 工廠方法
}
throw new BadMethodCallException("靜態方法 {$name} 不存在");
}
// 觸發
Device::create('iPad', new Battery());
public function __invoke($action, ...$params) {
if ($action === 'charge') {
$this->battery->setCharge($params[0]);
}
}
// 觸發
$device('charge', 80); // 如同呼叫 $device->__invoke('charge', 80)
function __autoload($className) {
$file = __DIR__ . '/' . $className . '.class.php';
if (file_exists($file)) require_once $file;
}
// 現代替代方案:spl_autoload_register() 或 Composer PSR-4 自動載入
| 魔術方法 | 觸發時機 | 典型用途 |
|---|---|---|
| __construct | new 實例化 | 初始化、依賴注入 |
| __destruct | 物件銷毀 | 資源釋放、關閉連線 |
| __get/__set/__isset/__unset | 存取不可見屬性 | 動態屬性、代理、資料映射 |
| __toString | echo/print/串接字串 | 物件字串化表示 |
| __clone | clone 關鍵字 | 深複製、重置資源 |
| __sleep/__wakeup | serialize/unserialize | 控制序列化內容、恢復連線 |
| __call/__callStatic | 呼叫不可見方法 | 動態代理、流暢介面、工廠 |
| __invoke | 物件當函式呼叫 | 可呼叫物件、策略模式 |
| __autoload | 類別找不到 | 舊專案相容(已棄用) |
@property、@method 標註動態屬性/方法,IDE 才能提示。__get/__set/__call 比直接存取慢,高頻路徑避免使用。__wakeup 中驗證資料完整性,防止反序列化攻擊(CVE-2016-7124 等)。魔術方法是 PHP 物件模型的強大擴充點,掌握其觸發機制與限制,能讓你設計出優雅的 DSL、動態代理、資源管理等進階架構。但請記住:明確優於隱性,除非確有需要,否則保持類別簡單、可預測。