在物件導向開發中,封裝性要求屬性設為 private 或 protected,但外部仍需存取。PHP 提供 __get() 與 __set() 魔術方法,實現統一的屬性存取攔截與驗證機制。
<?php
class Person {
private $__data = [];
public function __get($property) {
if (isset($this->__data[$property])) {
return $this->__data[$property];
}
return false; // 或拋出異常
}
public function __set($property, $value) {
// 可在此加入驗證邏輯
$this->__data[$property] = $value;
}
}
$person = new Person();
$person->name = '張三'; // 觸發 __set('name', '張三')
echo $person->name; // 觸發 __get('name') 輸出:張三
echo $person->age; // 不存在屬性,輸出:false
?>
__get/__set 只會在屬性為 private/protected 或不存在 時觸發。public 屬性直接存取,不會經過魔術方法。
<?php
class ParentClass {
protected $data = [];
public function __set($k, $v) { $this->data[$k] = $v; }
public function __get($k) { return $this->data[$k] ?? null; }
}
class ChildClass extends ParentClass {
public function __set($k, $v) {
// 父類 __set 不會被自動調用!
$this->data[$k] = strtoupper($v);
}
}
?>
解決方案:子類中手動呼叫 parent::__set($k, $v),或避免在繼承鏈中重寫魔術方法。
isset() / unset() 不觸發魔術方法直接對不可見屬性呼叫 isset($obj->prop) 永遠回傳 false,unset() 無效果。
<?php
class Entity {
protected $attributes = [];
public function __get($key) {
return $this->attributes[$key] ?? null;
}
public function __set($key, $value) {
$this->attributes[$key] = $value;
}
// 讓 isset()/empty() 生效
public function __isset($key) {
return isset($this->attributes[$key]);
}
// 讓 unset() 生效
public function __unset($key) {
unset($this->attributes[$key]);
}
}
$e = new Entity();
$e->name = 'Test';
var_dump(isset($e->name)); // true
unset($e->name);
var_dump(isset($e->name)); // false
?>
注意:__isset() / __unset() 需 PHP 5.1+ 版本支援。
$this->data 或 $this->attributes。__set() 中加入型別/格式驗證,拋出 InvalidArgumentException 而非靜默失敗。__call() 實作動態方法(如 findByName())。__get/__set 是 PHP 實現屬性存取攔截的核心機制,配合 __isset/__unset 可構建完整的動態屬性系統。但需注意繼承陷阱與可見性限制,合理使用能大幅提升程式碼彈性與維護性。