PHP 的魔術方法(常稱攔截器)讓物件能在執行期動態響應未定義的屬性與方法操作。本文針對 __get、__set、__call、__isset、__unset 五大核心攔截器,提供精簡範例與實務解析,幫助開發者快速掌握其行為差異與適用場景。
當嘗試讀取未宣告或不可見屬性時自動觸發。
class Lanjie {
public function __get($name) {
echo "$name property not found!";
}
}
$ob = new Lanjie();
echo $ob->g; // 輸出:g property not found!
典型用途:動態屬性代理、延遲載入、唯讀屬性保護。
搭配 method_exists 實現「設值自動轉發給 setXxx()」模式:
class Person {
private $_age;
private $_name;
public function __set($name, $value) {
$method = 'set' . ucfirst($name);
if (method_exists($this, $method)) {
return $this->$method($value);
}
}
public function setName($name) {
$this->_name = $name;
if (!is_null($this->_name)) {
$this->_name = strtoupper($this->_name); // 自動大寫
}
}
public function setAge($age) { $this->_age = (int)$age; }
}
$p = new Person();
$p->name = 'bob'; // 觸發 __set -> setName('bob') -> 儲存 'BOB'
print_r($p); // _name => 'BOB'
關鍵點:可在 setter 中加入驗證、轉換、事件觸發等橫切邏輯。
最常用、彈性最大的攔截器,常見於:
$api->getUserList() → HTTP GET)class ApiClient {
public function __call($method, $args) {
// 簡易 REST 映射:getXxx -> GET /xxx
if (preg_match('/^get(.+)/', $method, $m)) {
$endpoint = strtolower($m[1]);
return $this->request('GET', "/$endpoint", $args[0] ?? []);
}
throw new BadMethodCallException("Method $method not found");
}
private function request($verb, $uri, $params) { /* ... */ }
}
分別攔截 isset($obj->prop) / empty($obj->prop) 與 unset($obj->prop):
class Entity {
private $data = [];
public function __isset($k) { return array_key_exists($k, $this->data); }
public function __unset($k) { unset($this->data[$k]); }
public function __set($k, $v) { $this->data[$k] = $v; }
public function __get($k) { return $this->data[$k] ?? null; }
}
$e = new Entity();
$e->foo = 'bar';
var_dump(isset($e->foo)); // true
unset($e->foo);
var_dump(isset($e->foo)); // false
| 場景 | 推薦方案 | 備註 |
|---|---|---|
| 固定結構實體 | 明確宣告屬性 + getter/setter | IDE 支援、型別安全、效能最佳 |
| 動態屬性包(DTO、配置) | 陣列儲存 + __get/__set/__isset/__unset | 實作 ArrayAccess 更標準 |
| 方法級代理(RPC、Builder) | __call | 注意回傳 $this 實現鏈式呼叫 |
| 只讀/計算屬性 | __get + 私有快取 | 避免重複計算 |
__get 內部讀取 $this->$name 會再次觸發 __get → 必須操作私有儲存陣列或不同名屬性。@property、@method 註解或改用陣列存取。__sleep 需手動列出動態屬性鍵名,否則 serialize() 會遺失。__get,子類覆寫時別忘 parent::__get() 或完整接管邏輯。攔截器是 PHP 元編程的核心工具,__call 應用最廣,__get/__set 適合動態屬性封裝,__isset/__unset 補足語義完整性。實務上遵循「核心領域模型显式化,邊界適配層隱式化」原則,配合註解輔助靜態分析,即可兼得靈活與可維護性。