call_user_func_array 是 PHP 動態呼叫可呼叫單元的核心函數,配合 ... 運算子 (PHP 5.6+) 可實現參數陣列解包,廣泛應用於框架路由分發、事件系統、中介軟體管線、依賴注入容器等場景。本文從基本用法、進階模式、效能基準到最佳實踐全方位解析。
call_user_func_array(callable $callback, array $args): mixed
$callback:可呼叫類型(函數名字串、陣列 [$obj, 'method']、靜態 ['Class', 'method']、Closure、invokable 物件)。$args:數值索引陣列,元素依序對應參數。function sum(int ...$nums): int {
return array_sum($nums);
}
$result = call_user_func_array('sum', [[1, 2, 3, 4]]); // 10
// 等同於 sum(1, 2, 3, 4)
class Calculator {
public function add(float $a, float $b): float {
return $a + $b;
}
}
$calc = new Calculator();
$result = call_user_func_array([$calc, 'add'], [3.14, 2.86]); // 6.0
class Math {
public static function pow(float $base, float $exp): float {
return $base ** $exp;
}
}
$result = call_user_func_array(['Math', 'pow'], [2, 10]); // 1024
$multiplier = fn($x, $y) => $x * $y;
$result = call_user_func_array($multiplier, [5, 6]); // 30
// 綁定 $this
class Service {
private $factor = 10;
public function getMultiplier(): Closure {
return fn($x) => $x * $this->factor;
}
}
$svc = new Service();
$fn = $svc->getMultiplier();
echo call_user_func_array($fn, [7]); // 70
function swap(&$a, &$b) {
[$a, $b] = [$b, $a];
}
$x = 1; $y = 2;
call_user_func_array('swap', [&$x, &$y]); // 必須在陣列元素前加 &
echo "$x $y"; // 2 1
class Router {
private array $routes = [];
public function add(string $method, string $pattern, callable $handler): void {
$this->routes[] = compact('method', 'pattern', 'handler');
}
public function dispatch(string $method, string $uri): mixed {
foreach ($this->routes as $route) {
if ($route['method'] !== $method) continue;
if (!preg_match($route['pattern'], $uri, $matches)) continue;
array_shift($matches); // 移除完整匹配
return call_user_func_array($route['handler'], array_values($matches));
}
throw new Exception('404 Not Found');
}
}
$router = new Router();
$router->add('GET', '#/user/(\d+)#', function(int $id) {
return "User $id";
});
echo $router->dispatch('GET', '/user/42'); // User 42
class Pipeline {
private array $middlewares = [];
public function pipe(callable $middleware): self {
$this->middlewares[] = $middleware;
return $this;
}
public function handle(mixed $request): mixed {
$handler = function($req) use (&$handler) {
return $req; // 終點
};
// 由內而外包裹
foreach (array_reverse($this->middlewares) as $mw) {
$next = $handler;
$handler = fn($req) => $mw($req, $next);
}
return $handler($request);
}
}
// 使用
$app = new Pipeline();
$app->pipe(fn($req, $next) => ($req['auth'] ?? false) ? $next($req) : '401');
$app->pipe(fn($req, $next) => strtoupper($next($req)));
echo $app->handle(['auth' => true, 'data' => 'hello']); // HELLO
class Container {
private array $bindings = [];
public function bind(string $abstract, callable $concrete): void {
$this->bindings[$abstract] = $concrete;
}
public function make(string $abstract, array $params = []): mixed {
$concrete = $this->bindings[$abstract] ?? throw new Exception("Binding $abstract not found");
return call_user_func_array($concrete, array_merge([$this], $params));
}
}
$container = new Container();
$container->bind(PDO::class, fn(Container $c) => new PDO('sqlite::memory:'));
$pdo = $container->make(PDO::class);
| 呼叫方式 | 相對效能 (100萬次) | 備註 |
|---|---|---|
直接呼叫 fn() | 1.0x (基準) | 最快 |
call_user_func | ~1.3x | 參數固定 |
call_user_func_array | ~1.5x | 需建立陣列 |
$fn(...$args) (PHP 5.6+) | ~1.1x | 語法糖,編譯期優化 |
...$args。[] 配置。Closure::fromCallable 產生可快取的閉包。ArgumentCountError (PHP 7.1+),檢查陣列長度與函數簽名。&$var,否則無法修改外部變數。is_callable($cb) 先驗證,或用 Closure::fromCallable($cb) 統一轉為 Closure。// 直接取得 Closure,可直接呼叫或傳遞
$fn = strlen(...);
$fn = $obj->method(...);
$fn = ClassName::staticMethod(...);
// 配合 call_user_func_array
call_user_func_array($fn, ['hello']); // 5
call_user_func_array 是 PHP 動態特性的基石,掌握其用法與效能特徵,能讓你設計出靈活可擴充的框架級架構。日常業務邏輯優先使用直接呼叫或 ...$args,僅在路由、管線、容器等基礎設施層發揮其動態威力。