PHP **不支援**傳統意義上的函數重載 (同名不同參數列表)。但透過 **可變參數函數**、**func_get_args()/func_num_args()**、**預設參數值**、**__call() 魔術方法**,我們能優雅實現「同一介面、不同參數行為」的多型效果。本文系統解析 4 種核心模式與最佳實踐。
| 函數 | 用途 | 回傳值 |
|---|---|---|
func_num_args() | 取得實際傳入參數個數 | int |
func_get_args() | 取得所有參數組成的陣列 | array |
func_get_arg($index) | 取得指定索引參數 (0-based) | mixed |
**注意**:這三個函數**只能在函數內部使用**,PHP 8.0+ 標記為棄用,建議改用 `...$args` 可變參數語法。
<?php
function connect($host = 'localhost', $port = 3306, $user = 'root', $pass = '', $db = '') {
// 參數順序固定,呼叫時可省略尾部參數
// connect('db.example.com') → 使用預設 port/user/pass/db
// connect('db.example.com', 3307, 'admin') → 指定前三個
}
?>
<?php
function draw() {
$args = func_get_args();
$count = func_num_args();
switch ($count) {
case 1:
// draw($radius) → 畫圓
drawCircle($args[0]);
break;
case 2:
// draw($width, $height) → 畫矩形
drawRect($args[0], $args[1]);
break;
case 3:
// draw($x, $y, $radius) → 指定位置畫圓
drawCircleAt($args[0], $args[1], $args[2]);
break;
default:
throw new InvalidArgumentException("draw() 接受 1-3 參數,實際給了 $count");
}
}
function drawCircle($r) { echo "圓 半徑=$r\n"; }
function drawRect($w, $h) { echo "矩形 寬=$w 高=$h\n"; }
function drawCircleAt($x, $y, $r) { echo "圓 位置=($x,$y) 半徑=$r\n"; }
// 測試
draw(10); // 圓 半徑=10
draw(100, 50); // 矩形 寬=100 高=50
draw(10, 20, 5); // 圓 位置=(10,20) 半徑=5
?>
<?php
function sum(...$numbers) {
return array_sum($numbers);
}
echo sum(1, 2, 3); // 6
echo sum(1, 2, 3, 4, 5); // 15
// 類型提示 + 可變參數 (PHP 7.0+)
function logMessages(string ...$messages) {
foreach ($messages as $msg) {
error_log($msg);
}
}
logMessages('錯誤1', '警告2', '資訊3');
// 解包陣列傳入
$vals = [10, 20, 30];
echo sum(...$vals); // 60
?>
當呼叫**不存在的方法**時觸發,實現動態方法分派。
<?php
class Calculator {
public function __call($name, $arguments) {
$count = count($arguments);
// 方法名 + 參數數量 組合路由
$key = $name . '_' . $count;
$methods = [
'add_2' => fn($a, $b) => $a + $b,
'add_3' => fn($a, $b, $c) => $a + $b + $c,
'mul_2' => fn($a, $b) => $a * $b,
'concat_2' => fn($a, $b) => $a . $b,
];
if (isset($methods[$key])) {
return $methods[$key](...$arguments);
}
throw new BadMethodCallException("方法 $name 不支援 $count 個參數");
}
}
$calc = new Calculator();
echo $calc->add(1, 2); // 3
echo $calc->add(1, 2, 3); // 6
echo $calc->mul(5, 6); // 30
echo $calc->concat('Hello', 'World'); // HelloWorld
// $calc->add(1); // 拋出異常
?>
<?php
// PHP 8.0+ 原生命名參數,完美解決「跳過中間參數」問題
function createUser(string $name, int $age = 18, string $email = '', bool $active = true) {
// ...
}
// 呼叫時指定參數名,順序不拘、可跳過
createUser(name: '張三', email: 'zhang@example.com'); // age=18, active=true (預設)
createUser(name: '李四', age: 25, active: false); // email='' (預設)
?>
<?php
class Func {
// 靜態方法:根據參數類型/數量分派
public static function dispatch(string $prefix, array $args) {
$count = count($args);
$types = array_map(function($v) {
return is_int($v) ? 'int' : (is_string($v) ? 'str' : gettype($v));
}, $args);
$typeSig = implode('_', $types);
$method = $prefix . '_' . $count . '_' . $typeSig;
if (method_exists(self::class, $method)) {
return self::$method(...$args);
}
// 回退:嘗試僅按數量
$method = $prefix . '_' . $count;
if (method_exists(self::class, $method)) {
return self::$method(...$args);
}
throw new BadMethodCallException("無匹配處理器: $method");
}
// 定義具體實現
protected static function process_1_int($id) { return "查詢 ID=$id"; }
protected static function process_2_str_str($name, $email) { return "建立 $name <$email>"; }
protected static function process_3_int_str_str($id, $name, $email) { return "更新 ID=$id: $name <$email>"; }
}
// 使用
echo Func::dispatch('process', [123]); // 查詢 ID=123
echo Func::dispatch('process', ['張三', 'z@x.com']); // 建立 張三
echo Func::dispatch('process', [123, '李四', 'l@x.com']); // 更新 ID=123: 李四
?>
| 模式 | 效能 | 可讀性 | IDE 支援 | 適用場景 |
|---|---|---|---|---|
| 預設參數 | 極快 (原生) | ★★★★★ | 完美 | 參數固定順序、尾部可選 |
| func_get_args + switch | 快 (函數呼叫開銷) | ★★★☆☆ | 弱 (無參數提示) | 舊版本、參數數量區分邏輯 |
| ...$args 可變參數 | 快 (原生) | ★★★★☆ | 良好 (PHP 7.1+ 型別提示) | 不定數量同類參數、陣列解包 |
| __call 魔術方法 | 較慢 (魔術方法查找) | ★★☆☆☆ | 無 | 動態 API、代理模式、DSL |
| 命名參數 (PHP 8.0+) | 極快 (原生) | ★★★★★ | 完美 | **現代首選**、複雜參數組合 |
PHP 雖無傳統重載,但提供了**更靈活、更強大**的替代方案:
選擇原則:**優先使用語言原生特性 (命名參數、可變參數、預設值)**,只在動態性確有必要時才引入魔術方法或手動分派。這樣寫出的程式碼既優雅又高效,且易於維護與重構。