萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 魔術方法 __get() 與 __set() 實作屬性存取控制

PHP __get() 與 __set() 魔術方法深度解析

在物件導向開發中,封裝性要求屬性設為 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
?>

核心優勢

  1. 統一存取控制:集中處理屬性讀寫邏輯,如型別檢查、權限驗證、日誌記錄。
  2. 動態屬性支援:無需預先定義所有屬性,適合資料映射(ORM)、配置物件等場景。
  3. 防止任意屬性寫入:配合內部白名單陣列,僅允許合法屬性被設定。

常見陷阱與限制

1. 僅攔截不可見屬性

__get/__set 只會在屬性為 private/protected 或不存在 時觸發。public 屬性直接存取,不會經過魔術方法。

2. 繼承鏈破壞問題

<?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),或避免在繼承鏈中重寫魔術方法。

3. isset() / unset() 不觸發魔術方法

直接對不可見屬性呼叫 isset($obj->prop) 永遠回傳 false,unset() 無效果。

完整解決方案:實作 __isset() 與 __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())。
  • 文件化所有支援的動態屬性,避免團隊協作時的「隱形 API」。

總結

__get/__set 是 PHP 實現屬性存取攔截的核心機制,配合 __isset/__unset 可構建完整的動態屬性系統。但需注意繼承陷阱與可見性限制,合理使用能大幅提升程式碼彈性與維護性。

copyright © 萬盛學電腦網 all rights reserved