PHP 所謂的「重載」與 Java/C++ 不同,並非指方法簽名多態,而是指攔截不可訪問屬性/方法的魔術方法。通過 __get、__set、__call 等,可實現動態屬性、代理模式、只讀屬性、方法轉發等高級模式。本文全面解析 6 個重載魔術方法的觸發條件、參數、返回值與實戰應用。
| 方法 | 觸發場景 | 參數 | 返回值 |
|---|---|---|---|
__get($name) | 讀取不可訪問屬性(不存在、protected/private) | $name: 屬性名 | 屬性值(任意類型) |
__set($name, $value) | 寫入不可訪問屬性 | $name: 屬性名, $value: 值 | 無(void) |
__isset($name) | 對不可訪問屬性調用 isset() 或 empty() | $name: 屬性名 | bool |
__unset($name) | 對不可訪問屬性調用 unset() | $name: 屬性名 | 無(void) |
<?php
class DynamicEntity {
private array $data = [];
private array $allowed = ['name', 'email', 'age'];
public function __get(string $name) {
if (in_array($name, $this->allowed, true)) {
return $this->data[$name] ?? null;
}
throw new Exception("屬性 '$name' 不允許讀取");
}
public function __set(string $name, $value) {
if (in_array($name, $this->allowed, true)) {
$this->data[$name] = $value;
return;
}
throw new Exception("屬性 '$name' 不允許寫入");
}
public function __isset(string $name): bool {
return in_array($name, $this->allowed, true) && isset($this->data[$name]);
}
public function __unset(string $name) {
if (in_array($name, $this->allowed, true)) {
unset($this->data[$name]);
}
}
}
$user = new DynamicEntity();
$user->name = 'Alice'; // __set
echo $user->name; // __get -> Alice
echo isset($user->email); // __isset -> false
unset($user->name); // __unset
?>
| 方法 | 觸發場景 | 參數 | 返回值 |
|---|---|---|---|
__call($name, $arguments) | 調用不可訪問實例方法 | $name: 方法名, $arguments: 參數陣列 | 方法返回值 |
__callStatic($name, $arguments) | 調用不可訪問靜態方法 | $name: 方法名, $arguments: 參數陣列 | 方法返回值 |
<?php
class QueryBuilder {
private array $clauses = [];
public function __call(string $name, array $arguments) {
// 支援 where、orderBy、limit 等動態方法
if (in_array($name, ['where', 'orderBy', 'limit', 'select'], true)) {
$this->clauses[$name] = $arguments;
return $this; // 流式介面
}
throw new BadMethodCallException("方法 $name 不存在");
}
public function __callStatic(string $name, array $arguments) {
if ($name === 'table') {
$instance = new self();
$instance->clauses['table'] = $arguments;
return $instance;
}
throw new BadMethodCallException("靜態方法 $name 不存在");
}
public function getSql(): string {
// 簡化構建 SQL
$sql = 'SELECT ' . ($this->clauses['select'][0] ?? '*');
$sql .= ' FROM ' . ($this->clauses['table'][0] ?? 'unknown');
if (!empty($this->clauses['where'])) {
$sql .= ' WHERE ' . implode(' AND ', $this->clauses['where']);
}
if (!empty($this->clauses['orderBy'])) {
$sql .= ' ORDER BY ' . implode(', ', $this->clauses['orderBy']);
}
if (!empty($this->clauses['limit'])) {
$sql .= ' LIMIT ' . $this->clauses['limit'][0];
}
return $sql;
}
}
// 使用
$sql = QueryBuilder::table('users')
->where('id > 10')
->where('status = "active"')
->orderBy('created_at DESC')
->limit(10)
->getSql();
echo $sql;
// SELECT * FROM users WHERE id > 10 AND status = "active" ORDER BY created_at DESC LIMIT 10
?>
__set 拋出異常,__get 正常返回。__call 轉發給內部服務對象。__call 映射到新方法。__get 內部讀取同名屬性會無限遞歸,必須用內部儲存陣列(如 $this->data[$name])。parent::__call() 可調用父類邏輯。@property、@method 註解提示動態成員。__get、__set 可聲明返回類型與參數類型。<?php
class Example {
public function __call($name, $args) {
echo "實例方法: $name\n";
}
public static function __callStatic($name, $args) {
echo "靜態方法: $name\n";
}
}
$obj = new Example();
$obj->foo(); // 實例方法: foo
Example::bar(); // 靜態方法: bar
?>
PHP 重載機制通過 6 個魔術方法提供極大靈活性,是實現動態代理、ORM、流式 API 的關鍵。理解觸發條件、避免遞歸、配合註解提升 IDE 體驗,能安全發揮其威力。記住:只在真正需要動態性時使用,優先考慮顯式介面。