本文為《Core PHP Programming》筆記第十一節,深入講解 PHP 5 面向對象重載(Overloading)機制:__call() 攔截不可訪問方法調用、__set()/__get() 攔截不可訪問屬性讀寫、動態屬性存儲、參數類型分發實現偽重載、與 Java/C++ 重載的本質區別。
private/protected 且調用處無權限。public 成員直接訪問,不經魔術方法。class Overloader {
private $data = [];
// 屬性寫入攔截
public function __set(string $name, $value): void { $this->data[$name] = $value; }
// 屬性讀取攔截
public function __get(string $name) { return $this->data[$name] ?? null; }
// 方法調用攔截
public function __call(string $name, array $args) {
echo "Invoking $name() with args: "; print_r($args);
return true;
}
}
$o = new Overloader();
$o->dynaProp = "Dynamic"; // 觸發 __set
echo $o->dynaProp; // 觸發 __get
$o->dynaMethod("Leon", "Zeev"); // 觸發 __call
class DynamicEntity {
private $attributes = [];
public function __set($name, $value) { $this->attributes[$name] = $value; }
public function __get($name) { return $this->attributes[$name] ?? null; }
public function __isset($name) { return isset($this->attributes[$name]); }
public function __unset($name) { unset($this->attributes[$name]); }
public function toArray() { return $this->attributes; }
}
$e = new DynamicEntity();
$e->title = "Hello";
$e->views = 123;
echo $e->title; // Hello
var_dump($e->toArray());
__isset/__unset 完整支持 isset()/unset() 語法。class Math {
public function __call($name, $args) {
if ($name === 'add') {
if (count($args) === 2 && is_int($args[0]) && is_int($args[1])) {
return $this->addInt($args[0], $args[1]);
}
if (count($args) === 2 && is_float($args[0]) && is_float($args[1])) {
return $this->addFloat($args[0], $args[1]);
}
if (count($args) === 3 && is_array($args[0])) {
return $this->addArray($args[0]);
}
}
throw new BadMethodCallException("Method $name not supported with these args");
}
private function addInt($a, $b) { return $a + $b; }
private function addFloat($a, $b) { return $a + $b; }
private function addArray(array $arr) { return array_sum($arr); }
}
$m = new Math();
echo $m->add(1, 2); // 3 (int)
echo $m->add(1.5, 2.5); // 4.0 (float)
echo $m->add([1,2,3]); // 6 (array)
class Test {
private $secret = 42;
public function __get($name) {
if ($name === 'secret') return '***'; // 過濾私有屬性外部讀取
return null;
}
}
$t = new Test();
echo $t->secret; // *** (觸發 __get,因 private 不可外部直接讀)
echo $t->publicProp; // null (不存在也觸發 __get)
| 場景 | 建議 |
|---|---|
| 固定結構實體 | 顯式聲明屬性 + getter/setter,避免魔術方法開銷 |
| 動態/擴展屬性 | 使用 __set/__get + 內部數組存儲 |
| API 代理/遠程調用 | __call 轉發至 RPC 客戶端 |
| 框架核心/基類 | 謹慎使用,文檔化所有動態行為 |