| 方法 | 觸發時機 | 典型用途 |
|---|---|---|
__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+) | 控制除錯輸出、隱藏敏感資訊 |
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;
}
}
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]);
}
}
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 不存在");
}
}
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();
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 呼叫
}
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');
}
}
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()]
]);
}
}
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 = [];
}
}
@property、@method 註解讓 IDE 支援自動完成__get/__call 找不到時拋出明確異常,而非靜默返回 null__toString 中勿觸發另一個 __toString,會導致致命錯誤PHP 16 個魔術方法構成了物件生命週期、存取控制、序列化、除錯的完整攔截點。掌握它們能讓你寫出優雅的 DSL、Active Record、Proxy、Value Object 等進階模式。但記住:魔術方法是語法糖,不是銀彈——清晰的介面定義永遠優於隱式行為。