利用 `__get`、`__set`、`__isset`、`__unset` 四個魔術方法,可攔截不可見屬性的存取,實現白名單控制、資料驗證、唯讀屬性、動態屬性等進階功能。本文透過完整類別範例展示實作細節。
<?php
class Person {
public $name;
private $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
// 讀取攔截:僅允許讀取 $age
public function __get($param) {
$allow = ['age'];
if (in_array($param, $allow)) {
return $this->$param;
}
return null; // 或拋出異常
}
// 寫入攔截:僅允許寫入 $age、$money
public function __set($name, $val) {
$allow = ['age', 'money'];
if (in_array($name, $allow)) {
$this->$name = $val;
}
}
// isset()/empty() 攔截
public function __isset($name) {
return isset($this->$name);
}
// unset() 攔截:僅允許刪除 $tail
public function __unset($name) {
$allow = ['tail'];
if (in_array($name, $allow)) {
echo 'unset running
';
unset($this->$name);
}
}
}
// 測試
$p = new Person('zp', 21);
$p->age = 200; // 允許寫入私有屬性
echo $p->money = 200000000; // 動態建立 money 屬性
$p->tail = "a"; // 動態建立 tail
var_dump(isset($p->name)); // true (public)
var_dump(isset($p->age)); // true (經由 __isset)
var_dump(isset($p->money)); // true (動態屬性)
unset($p->tail); // 觸發 __unset,輸出 "unset running"
// unset($p->age); // 不在允許列表,靜默忽略
?>
| 場景 | 實現方式 |
|---|---|
| 唯讀屬性 | `__set` 拋出異常或靜默忽略 |
| 資料驗證/過濾 | `__set` 內檢查型別、長度、格式 |
| 動態屬性代理 | `__get`/`__set` 轉發至陣列/資料庫/快取 |
| 屬性存取日誌 | 在魔術方法中記錄存取時間、調用堆疊 |
| 舊代碼相容 | 映射舊屬性名至新屬性 |
public function __call($method, $args) {
if (str_starts_with($method, 'get')) {
$prop = lcfirst(substr($method, 3));
return $this->__get($prop);
}
if (str_starts_with($method, 'set')) {
$prop = lcfirst(substr($method, 3));
return $this->__set($prop, $args[0]);
}
throw new BadMethodCallException("Method $method not found");
}
善用魔術方法能大幅減少樣板代碼,實現靈活的物件行為控制,但需權衡可讀性與效能。