首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 魔術方法完整指南:16 個核心方法功能、觸發時機與實戰範例

     編輯:PHP 綜合知識

魔術方法總覽

方法觸發時機典型用途
__construct()物件實例化時初始化屬性、建立連線、驗證參數
__destruct()物件銷毀時(無引用/腳本結束)關閉連線、釋放資源、記錄日誌
__call()呼叫不可見/不存在的實例方法方法代理、動態方法、 fluent 介面
__callStatic()呼叫不可見/不存在的靜態方法靜態代理、工廠模式
__get()讀取不可見/不存在的屬性唯讀屬性、延遲載入、資料轉換
__set()寫入不可見/不存在的屬性屬性驗證、自動持久化、虛擬屬性
__isset()對不可見屬性呼叫 isset()/empty()虛擬屬性存在性檢查
__unset()對不可見屬性呼叫 unset()虛擬屬性刪除邏輯
__toString()物件當作字串使用日誌輸出、除錯顯示、模板渲染
__invoke()物件當作函數呼叫可呼叫物件、策略模式、閉包替代
__clone()clone 物件時深度複製、重置資源 ID、斷開共享引用
__sleep()serialize() 物件時指定序列化屬性、關閉連線、清理暫存
__wakeup()unserialize() 物件時重建連線、初始化資源、驗證完整性
__set_state()var_export() 物件時靜態工廠重建物件
__debugInfo()var_dump() 物件時 (PHP 5.6+)控制除錯輸出、隱藏敏感資訊

核心方法深度解析與實戰

1. 建構與解構

class Database {
    private $pdo;
    
    public function __construct(array $config) {
        // 單例模式防護
        if (static::$instance) {
            throw new Exception('請使用 getInstance()');
        }
        $this->pdo = new PDO(
            "mysql:host={$config['host']};dbname={$config['dbname']}",
            $config['user'], $config['pass'],
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
        );
    }
    
    public function __destruct() {
        // PHP 會自動關閉 PDO 連線,但顯式關閉更明確
        $this->pdo = null;
    }
}

2. 屬性重載(__get/__set/__isset/__unset)

class Entity {
    private $data = [];
    private $original = [];
    private $casts = [];  // 型別轉換規則
    
    public function __get($name) {
        // 1. 檢查是否為定義的屬性
        if (property_exists($this, $name)) {
            return $this->$name;
        }
        
        // 2. 從資料陣列讀取
        if (array_key_exists($name, $this->data)) {
            $value = $this->data[$name];
            // 型別轉換
            return $this->castAttribute($name, $value);
        }
        
        // 3. 關聯載入(如 $user->posts)
        return $this->loadRelation($name);
    }
    
    public function __set($name, $value) {
        // 觸發觀察者/事件
        $this->fireModelEvent('setting', [$name, $value]);
        
        // 型別轉換後儲存
        $this->data[$name] = $this->castAttribute($name, $value, true);
        
        // 標記為髒資料
        $this->dirty[$name] = true;
    }
    
    public function __isset($name) {
        return array_key_exists($name, $this->data) 
            || method_exists($this, 'get' . ucfirst($name) . 'Attribute');
    }
    
    public function __unset($name) {
        unset($this->data[$name], $this->dirty[$name], $this->original[$name]);
    }
}

3. 方法代理(__call/__callStatic)

class Repository {
    private $queryBuilder;
    
    public function __call($method, $args) {
        // 動態 where 方法:whereName('John') -> where('name', 'John')
        if (preg_match('/^where(.+)$/', $method, $m)) {
            $field = lcfirst($m[1]);  // Name -> name
            return $this->queryBuilder->where($field, $args[0] ?? null);
        }
        
        // 動態 findBy 方法:findByEmail($email)
        if (preg_match('/^findBy(.+)$/', $method, $m)) {
            $field = lcfirst($m[1]);
            return $this->queryBuilder->where($field, $args[0])->first();
        }
        
        throw new BadMethodCallException("Method $method 不存在");
    }
    
    public static function __callStatic($method, $args) {
        // 靜態工廠:User::create(['name' => 'John'])
        if ($method === 'create') {
            return (new static())->forceFill($args[0])->save();
        }
        throw new BadMethodCallException("Static method $method 不存在");
    }
}

4. 物件字串化(__toString)

class Money {
    private int $cents;
    private string $currency;
    
    public function __toString(): string {
        return sprintf('%.2f %s', $this->cents / 100, $this->currency);
    }
}

$price = new Money(999, 'USD');
echo "價格: $price";  // 價格: 9.99 USD
// 等同 echo "價格: " . $price->__toString();

5. 可呼叫物件(__invoke)

class RateLimiter {
    private int $maxAttempts;
    private int $decayMinutes;
    private array $attempts = [];
    
    public function __invoke(string $key): bool {
        $now = time();
        $this->attempts[$key] = array_filter(
            $this->attempts[$key] ?? [], 
            fn($t) => $now - $t < $this->decayMinutes * 60
        );
        
        if (count($this->attempts[$key]) >= $this->maxAttempts) {
            return false;  // 限流
        }
        
        $this->attempts[$key][] = $now;
        return true;  // 允許
    }
}

$limiter = new RateLimiter(5, 1);
if ($limiter('api:user:123')) {
    // 執行 API 呼叫
}

6. 複製控制(__clone)

class Order {
    public int $id;
    public array $items = [];
    private ?string $lockToken = null;
    
    public function __clone() {
        // 1. 重置 ID(新訂單)
        $this->id = 0;
        
        // 2. 深度複製項目陣列
        $this->items = array_map(fn($item) => clone $item, $this->items);
        
        // 3. 重置鎖定狀態
        $this->lockToken = null;
        
        // 4. 觸發事件
        $this->fireModelEvent('cloned');
    }
}

7. 序列化控制(__sleep/__wakeup)

class ServiceClient {
    private $httpClient;      // Guzzle client - 不可序列化
    private string $apiKey;   // 敏感資訊 - 不應序列化
    public array $config = []; // 可序列化
    
    public function __sleep(): array {
        // 僅返回允許序列化的屬性名
        return ['config'];
    }
    
    public function __wakeup(): void {
        // 反序列化後重建資源
        $this->httpClient = new GuzzleHttp\Client([
            'base_uri' => $this->config['base_uri'],
            'headers' => ['Authorization' => 'Bearer ' . $this->getApiKeyFromVault()]
        ]);
    }
}

8. 除錯資訊控制(__debugInfo)

class User {
    public int $id;
    public string $email;
    private string $passwordHash;
    private string $apiToken;
    
    public function __debugInfo(): array {
        return [
            'id' => $this->id,
            'email' => $this->email,
            // 敏感欄位不顯示或遮罩
            'password_hash' => '***HIDDEN***',
            'api_token' => substr($this->apiToken, 0, 4) . '****',
        ];
    }
}

var_dump($user);
// 輸出僅包含 __debugInfo 返回的鍵值

進階模式:組合使用

// Active Record 基類雛形
abstract class Model {
    use HasAttributes, HasRelationships, HasEvents;
    
    // 建構時自動載入原始資料
    public function __construct(array $attributes = []) {
        $this->fill($attributes);
        $this->syncOriginal();
    }
    
    // 序列化時只存儲屬性與關聯 ID
    public function __sleep() {
        return array_merge(['attributes', 'original', 'relations'], 
                          array_keys($this->casts));
    }
    
    // 反序列化後重建關聯
    public function __wakeup() {
        $this->bootIfNotBooted();
    }
    
    // 克隆時重置 ID 與時間戳
    public function __clone() {
        $this->id = null;
        $this->created_at = null;
        $this->updated_at = null;
        $this->relations = [];
    }
}

效能與最佳實踐

  • 避免濫用:魔術方法比直接存取慢 2-5 倍,熱路徑優先用定義屬性/方法
  • 文檔化:使用 @property、@method 註解讓 IDE 支援自動完成
  • 錯誤處理:__get/__call 找不到時拋出明確異常,而非靜默返回 null
  • 遞迴防護:__toString 中勿觸發另一個 __toString,會導致致命錯誤

總結

PHP 16 個魔術方法構成了物件生命週期、存取控制、序列化、除錯的完整攔截點。掌握它們能讓你寫出優雅的 DSL、Active Record、Proxy、Value Object 等進階模式。但記住:魔術方法是語法糖,不是銀彈——清晰的介面定義永遠優於隱式行為。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved