PHP 5 起提供以 __ 雙底線開頭的魔術方法,在特定生命週期自動觸發。建議自訂方法避免雙底線開頭,防止衝突。
| 方法 | 觸發時機 | 參數 | 回傳值要求 |
|---|---|---|---|
__construct | 物件實例化時 | 任意 | 無 |
__destruct | 物件銷毀前 (GC/腳本結束) | 無 | 無 |
__call | 呼叫不可存取/不存在的實例方法 | $method, $args | 任意 |
__callStatic | 呼叫不可存取/不存在的靜態方法 (PHP 5.3+) | $method, $args | 任意 |
__get | 讀取不可存取/不存在的屬性 | $name | 任意 |
__set | 寫入不可存取/不存在的屬性 | $name, $value | 無 |
__isset | 對不可存取屬性呼叫 isset()/empty() | $name | bool |
__unset | 對不可存取屬性呼叫 unset() | $name | 無 |
__sleep | serialize() 前 | 無 | array (需序列化的屬性名) |
__wakeup | unserialize() 後 | 無 | 無 |
__toString | 物件當作字串使用 (echo/print/字串串接) | 無 | string |
__invoke | 物件當作函數呼叫 $obj() (PHP 5.3+) | 任意 | 任意 |
__set_state | var_export() 匯出類別時 (PHP 5.1+) | $properties | 物件實例 |
__clone | clone 複製物件後 | 無 | 無 |
__debugInfo | var_dump() 輸出物件時 (PHP 5.6+) | 無 | array |
class Entity {
private $data = [];
public function __get($name) {
return $this->data[$name] ?? null;
}
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name]);
}
}
$e = new Entity();
$e->name = 'Tom'; // 觸發 __set
echo $e->name; // 觸發 __get
isset($e->name); // 觸發 __isset
unset($e->name); // 觸發 __unset
注意:__get 回傳值不會被引用,$e->arr[] = 1 會失效,需改用 $tmp = $e->arr; $tmp[] = 1; $e->arr = $tmp; 或實作 ArrayAccess。
class Service {
public function __call($method, $args) {
if (strpos($method, 'get') === 0) {
$field = lcfirst(substr($method, 3));
return $this->fetch($field);
}
throw new BadMethodCallException("Method $method not found");
}
static public function __callStatic($method, $args) {
// 靜態代理邏輯
}
private function fetch($field) { return "data:$field"; }
}
$s = new Service();
echo $s->getName(); // 輸出 "data:name"
class DbConnection {
protected $link;
protected $dsn, $user, $pass;
public function __construct($dsn, $user, $pass) {
$this->dsn = $dsn; $this->user = $user; $this->pass = $pass;
$this->connect();
}
public function __sleep() {
return ['dsn', 'user', 'pass']; // 不序列化 $link (resource 不可序列化)
}
public function __wakeup() {
$this->connect(); // 反序列化後自動重連
}
private function connect() { /* ... */ }
}
class Money {
private $amount, $currency;
public function __construct($amount, $currency) { $this->amount = $amount; $this->currency = $currency; }
public function __toString() {
return number_format($this->amount, 2) . ' ' . $this->currency;
}
}
$price = new Money(1999.99, 'USD');
echo $price; // "1,999.99 USD"
echo "Price: $price"; // 字串插值也觸發
PHP 5.2+ 任何字串語境 (printf %s) 皆觸發;未實作則拋出 E_RECOVERABLE_ERROR。
class Multiplier {
private $factor;
public function __construct($factor) { $this->factor = $factor; }
public function __invoke($x) { return $x * $this->factor; }
}
$double = new Multiplier(2);
echo $double(5); // 10
// 可作為 callable 傳遞
array_map(new Multiplier(3), [1,2,3]); // [3,6,9]
class Order {
public $id;
public $items; // 陣列物件
public function __clone() {
$this->id = uniqid(); // 新訂單號
$this->items = array_map(fn($item) => clone $item, $this->items); // 深層複製商品
}
}
$newOrder = clone $oldOrder;
PHP 7.2 棄用 __autoload,PHP 8.0 移除。 請一律使用
spl_autoload_register()並配合 Composer PSR-4 自動載入。
// 現代做法:composer.json 定義 autoload,無需手寫
// 手動註冊範例 (不建議)
spl_autoload_register(function ($class) {
$file = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php';
if (file_exists($file)) require $file;
});
class Config {
public $settings = [];
public static function __set_state($arr) {
$obj = new self();
$obj->settings = $arr['settings'];
return $obj;
}
}
$config = new Config(); $config->settings = ['debug' => true];
eval('$c = ' . var_export($config, true) . ';'); // $c 為新 Config 實例
__call/__get 等攔截器有微小開銷,高頻路徑優先定義明確方法/屬性。ArrayAccess, Iterator, Serializable, Countable) 解決的優先用介面,語意更明確。__autoload 中拋出的例外無法被 catch (PHP 7 前),現代自動載入器內部應自行 try-catch 並轉為錯誤日誌。