在閱讀 UCenter 原始碼時,開發者常會遇到 call_user_func 這個函數,初以為是專案自定義的功能,卻在代碼庫中搜尋不到定義。原來它是 PHP 內建的核心函數,允許以字串或陣列形式動態呼叫使用者定義的函數、類別方法,並支援參數傳遞與引用操作。本文將系統整理這兩個函數的語法差異、呼叫慣例與實戰技巧。
call_user_func 接受回呼名稱作為第一個參數,後續參數直接對應目標函數的參數列表:
<?php
function greet($name, $suffix) {
echo $name . $suffix;
}
// 直接傳遞多個參數
call_user_func('greet', 'Alice', '!'); // 輸出 Alice!
call_user_func('greet', 'Bob', '!!'); // 輸出 Bob!!
?>
呼叫類別方法時,第一個參數需為陣列:['ClassName', 'methodName'](靜態)或 [$instance, 'methodName'](實例)。這種設計避免了 new ClassName() 的額外開銷,同時保持呼叫介面一致。
<?php
class Calculator {
public static function add($a, $b) { return $a + $b; }
public function mul($a, $b) { return $a * $b; }
}
// 靜態方法
echo call_user_func(['Calculator', 'add'], 3, 4); // 7
// 實例方法
$calc = new Calculator();
echo call_user_func([$calc, 'mul'], 3, 4); // 12
?>
call_user_func_array 僅接受兩個參數:回呼與參數陣列。當參數數量動態或需批次處理時,結構更清晰:
<?php
function concat($sep, ...$parts) {
return implode($sep, $parts);
}
$args = ['-', '2024', '05', '20'];
echo call_user_func_array('concat', $args); // 2024-05-20
?>
同樣支援類別方法:
<?php
class Math {
public static function sum(...$nums) {
return array_sum($nums);
}
}
echo call_user_func_array(['Math', 'sum'], [[1,2,3,4,5]]); // 15
?>
兩個函數均支援引用參數,行為與直接呼叫一致:
<?php
function increment(&$val) { $val++; }
$num = 10;
call_user_func('increment', $num);
echo $num; // 11
call_user_func_array('increment', [&$num]);
echo $num; // 12
?>
注意:PHP 5.3+ 已棄用 call_user_method 系列,請改用上述標準函數。
在框架路由分發、事件監聽器、中介軟體鏈中,常見以下模式:
<?php
$routes = [
'GET /user' => ['UserController', 'index'],
'POST /user' => ['UserController', 'store'],
];
$handler = $routes[$method . ' ' . $path] ?? null;
if ($handler) {
call_user_func_array($handler, $requestParams);
}
?>
搭配 is_callable() 可預先驗證回呼有效性,提升健壯性。
雖然 call_user_func* 靈活,但比直接呼叫慢約 2-3 倍(因內部需解析回呼)。PHP 7+ 建議改用:
$fn = fn() => $obj->method(...$args); $fn();...$args 解包語法:$obj->method(...$params);__invoke() 魔術方法實現可呼叫物件。但在需「字串決定呼叫目標」的動態場景(如插件系統、指令總線),call_user_func* 仍是標準解法。