PHP 保留雙底線 __ 開頭的方法名為魔術方法,共 16 種 (含 __invoke/__callStatic),觸發時機各異,核心用途在於物件生命週期攔截、屬性/方法動態存取、序列化控制等。
class PropertyInterceptor {
private $data = [];
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
trigger_error("屬性 '$name' 不存在", E_USER_NOTICE);
return 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]);
}
}
$obj = new PropertyInterceptor();
$obj->name = "測試"; // 觸發 __set
echo $obj->name; // 觸發 __get
isset($obj->name); // 觸發 __isset
unset($obj->name); // 觸發 __unset
注意: 這四個方法只對「未宣告」或「不可見」(private/protected) 屬性生效。
class MethodInterceptor {
public function __call($method, $args) {
echo "實例方法 '$method' 不存在,參數: " . json_encode($args) . "\n";
}
public static function __callStatic($method, $args) {
echo "靜態方法 '$method' 不存在,參數: " . json_encode($args) . "\n";
}
}
$obj = new MethodInterceptor();
$obj->undefinedMethod(1, 2, 3); // 觸發 __call
MethodInterceptor::staticUndefined('a', 'b'); // 觸發 __callStatic (PHP 5.3+)
規則: 先找本類方法 → 父類方法 → 本類 __call → 父類 __call。
class LifeCycle {
public function __construct($name) {
$this->name = $name;
echo "[$this->name] 建構完成\n";
}
public function __destruct() {
echo "[$this->name] 解構中...\n";
// 釋放資源、關閉連線、寫入日誌等
}
}
$obj = new LifeCycle("A"); // 輸出: [A] 建構完成
unset($obj); // 輸出: [A] 解構中...
// 全域變數在腳本結束時觸發 __destruct
觸發時機: 函數內區域變數在 return 時;全域變數在腳本結束時;unset() 或賦值 null 立即觸發。
class CloneDemo {
public $id;
private $resource;
public function __construct() {
$this->resource = fopen("log.txt", "w");
}
public function __clone() {
// 複製時重新開啟資源,避免共用同一檔案控制代碼
$this->resource = fopen("log.txt", "w");
$this->id = uniqid(); // 重新生成唯一 ID
}
}
$obj1 = new CloneDemo();
$obj2 = clone $obj1; // 觸發 __clone
class ToStringDemo {
private $value;
public function __construct($v) { $this->value = $v; }
public function __toString() {
return "ToStringDemo: {$this->value}";
}
}
$obj = new ToStringDemo(123);
echo $obj; // 直接輸出物件 → 觸發 __toString
echo "值: $obj"; // 字串插值 → 觸發 __toString
printf("%s", $obj); // PHP 5.2+ 支援 %s 修飾符
限制: 無法用於非字串語境 (如 %d、陣列索引、數學運算)。
class SerializeControl {
public $data = [];
private $dbLink;
public function __sleep() {
// 關閉資料庫連線,只序列化 $data
$this->dbLink = null;
return ['data']; // 必須回傳需序列化的屬性名陣列
}
public function __wakeup() {
// 反序列化後重建連線
$this->dbLink = new PDO(...);
}
}
class StateExport {
public $a = 1;
public $b = 2;
public static function __set_state($arr) {
$obj = new self();
$obj->a = $arr['a'];
$obj->b = $arr['b'];
return $obj;
}
}
$obj = new StateExport();
eval('$new = ' . var_export($obj, true) . ';'); // 觸發 __set_state
class CallableObject {
public function __invoke($x, $y) {
return $x + $y;
}
}
$fn = new CallableObject();
echo $fn(3, 5); // 輸出 8,物件可直接像函數一樣呼叫
// 單一自動載入函數 (不推薦)
function __autoload($class) {
include "classes/$class.php";
}
// 現代做法:註冊多個載入器
spl_autoload_register(function($class) {
// PSR-4 風格載入邏輯
});
class Loader {
public static function autoload($class) { /* ... */ }
}
spl_autoload_register(['Loader', 'autoload']);
警告: __autoload 中拋出的異常無法被 catch 捕獲,會導致致命錯誤。
__callStatic 必須同時宣告 public static