call_user_func / call_user_func_array
概述
call_user_func() 和 call_user_func_array() 是 PHP 中用于动态调用函数的两个内置函数。它们允许通过函数名(字符串)或回调来调用函数,参数可以分别传递或以数组形式传递。在现代 PHP 中,这些函数已被参数展开(...)和可变函数语法大量替代。
PHP 版本说明
call_user_func()和call_user_func_array()自 PHP 4 起可用- PHP 5.4+ 支持闭包作为回调
- PHP 7.0+ 支持严格类型在回调中使用
- PHP 8.1+ 的 First-class 可调用语法
func(...)提供了更现代的替代
基础概念
动态调用方式对比
| 方式 | 语法 | PHP 版本 | 说明 |
|---|---|---|---|
| 可变函数 | $func(...) | 所有版本 | 最简洁 |
call_user_func | call_user_func($func, ...$args) | PHP 4+ | 逐个传参 |
call_user_func_array | call_user_func_array($func, $args) | PHP 4+ | 数组传参 |
| 参数展开 | $func(...$args) | PHP 5.6+ | 现代替代 |
| First-class | $func = func(...) | PHP 8.1+ | 最现代 |
语法与代码
call_user_func() 基本用法
php
<?php
declare(strict_types=1);
// 调用普通函数
function greet(string $name): string
{
return "Hello, {$name}!";
}
echo call_user_func('greet', 'Alice') . "\n"; // Hello, Alice!
// 调用带多个参数的函数
function add(int $a, int $b): int
{
return $a + $b;
}
echo call_user_func('add', 10, 20) . "\n"; // 30
// 嵌套调用
function format(string $text): string
{
return strtoupper($text);
}
echo call_user_func('format', call_user_func('greet', 'Bob')) . "\n";
// HELLO, BOB!call_user_func_array() 用法
php
<?php
declare(strict_types=1);
// 参数以数组传递
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
$args = [1, 2, 3, 4, 5];
echo call_user_func_array('sum', $args) . "\n"; // 15
// 动态构建参数
function query(string $table, array $where, string $orderBy, int $limit): string
{
$sql = "SELECT * FROM {$table}";
if (!empty($where)) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$sql .= " ORDER BY {$orderBy} LIMIT {$limit}";
return $sql;
}
$params = ['users', ['status' => 'active'], 'created_at DESC', 10];
echo call_user_func_array('query', $params) . "\n";
// SELECT * FROM users WHERE status = 'active' ORDER BY created_at DESC LIMIT 10
// 参数为空数组
echo call_user_func_array('strlen', []) . "\n"; // 0调用闭包
php
<?php
declare(strict_types=1);
// call_user_func 调用闭包
$multiply = fn(int $a, int $b): int => $a * $b;
echo call_user_func($multiply, 3, 4) . "\n"; // 12
// call_user_func_array 调用闭包
$formatName = function (string $first, string $last, string $separator = ' '): string {
return "{$first}{$separator}{$last}";
};
echo call_user_func_array($formatName, ['John', 'Doe']) . "\n"; // John Doe调用对象方法
php
<?php
declare(strict_types=1);
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
public static function multiply(int $a, int $b): int
{
return $a * $b;
}
}
$calc = new Calculator();
// 调用实例方法
echo call_user_func([$calc, 'add'], 10, 20) . "\n"; // 30
echo call_user_func_array([$calc, 'add'], [10, 20]) . "\n"; // 30
// 调用静态方法
echo call_user_func(['Calculator', 'multiply'], 5, 6) . "\n"; // 30
echo call_user_func_array(['Calculator', 'multiply'], [5, 6]) . "\n"; // 30
// 使用类名字符串
echo call_user_func('Calculator::multiply', 3, 7) . "\n"; // 21详细说明
与可变函数和参数展开的对比
php
<?php
declare(strict_types=1);
function multiply(int $a, int $b): int
{
return $a * $b;
}
// 方式 1:直接调用
echo multiply(3, 4) . "\n"; // 12
// 方式 2:可变函数
$func = 'multiply';
echo $func(3, 4) . "\n"; // 12
// 方式 3:call_user_func
echo call_user_func('multiply', 3, 4) . "\n"; // 12
// 方式 4:call_user_func_array
echo call_user_func_array('multiply', [3, 4]) . "\n"; // 12
// 方式 5:参数展开(PHP 5.6+)
$args = [3, 4];
echo multiply(...$args) . "\n"; // 12
// 方式 6:First-class callable(PHP 8.1+)
$multiplyRef = multiply(...);
echo $multiplyRef(3, 4) . "\n"; // 12性能差异
call_user_func 系列函数比直接调用和可变函数略慢,因为涉及额外的函数调用开销。
php
<?php
declare(strict_types=1);
function simpleFunc(int $n): int
{
return $n * 2;
}
$iterations = 100000;
// 直接调用(最快)
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
simpleFunc($i);
}
$direct = microtime(true) - $start;
// 可变函数
$func = 'simpleFunc';
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$func($i);
}
$variable = microtime(true) - $start;
// call_user_func
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
call_user_func('simpleFunc', $i);
}
$cuf = microtime(true) - $start;
// call_user_func_array
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
call_user_func_array('simpleFunc', [$i]);
}
$cufa = microtime(true) - $start;
echo "Direct: " . number_format($direct * 1000, 1) . " ms\n";
echo "Variable: " . number_format($variable * 1000, 1) . " ms\n";
echo "call_user_func: " . number_format($cuf * 1000, 1) . " ms\n";
echo "call_user_func_array: " . number_format($cufa * 1000, 1) . " ms\n";性能建议
在现代 PHP 中,推荐使用可变函数 $func() 或参数展开 func(...$args) 替代 call_user_func 系列,以获得更好的性能。
命名参数兼容
call_user_func_array 在 PHP 8.0+ 中支持通过关联数组传递命名参数。
php
<?php
declare(strict_types=1);
function configure(
string $host = 'localhost',
int $port = 3306,
bool $debug = false
): string {
return "host={$host}, port={$port}, debug=" . ($debug ? 'true' : 'false');
}
// PHP 8.0+ 支持命名参数数组
$params = [
'port' => 5432,
'debug' => true,
];
echo call_user_func_array('configure', $params) . "\n";
// host=localhost, port=5432, debug=true实战示例
插件系统
php
<?php
declare(strict_types=1);
class PluginManager
{
/** @var array<string, callable> */
private array $plugins = [];
public function register(string $name, callable $handler): void
{
$this->plugins[$name] = $handler;
}
public function execute(string $name, array $params = []): mixed
{
if (!isset($this->plugins[$name])) {
throw new RuntimeException("Plugin '{$name}' not registered");
}
return call_user_func_array($this->plugins[$name], $params);
}
public function hasPlugin(string $name): bool
{
return isset($this->plugins[$name]);
}
}
$manager = new PluginManager();
// 注册插件
$manager->register('upper', fn(string $text): string => strtoupper($text));
$manager->register('lower', fn(string $text): string => strtolower($text));
$manager->register('reverse', fn(string $text): string => strrev($text));
$manager->register('repeat', fn(string $text, int $n): string => str_repeat($text, $n));
// 执行插件
echo $manager->execute('upper', ['hello world']) . "\n"; // HELLO WORLD
echo $manager->execute('reverse', ['hello']) . "\n"; // olleh
echo $manager->execute('repeat', ['ab', 3]) . "\n"; // ababab回调队列处理器
php
<?php
declare(strict_types=1);
class CallbackQueue
{
/** @var callable[] */
private array $callbacks = [];
public function push(callable $callback): void
{
$this->callbacks[] = $callback;
}
public function processAll(mixed $initial): mixed
{
$result = $initial;
foreach ($this->callbacks as $callback) {
$result = call_user_func($callback, $result);
}
return $result;
}
public function processAllWithArgs(array $args): mixed
{
foreach ($this->callbacks as $callback) {
$args = [call_user_func_array($callback, $args)];
}
return $args[0];
}
}
$queue = new CallbackQueue();
$queue->push(fn(string $s): string => trim($s));
$queue->push(fn(string $s): string => strtolower($s));
$queue->push(fn(string $s): string => ucfirst($s));
echo $queue->processAll(' HELLO WORLD ') . "\n"; // Hello world注意事项
常见陷阱
- call_user_func 在严格模式下的类型检查
php
<?php
declare(strict_types=1);
function expectInt(int $value): int
{
return $value;
}
// 严格模式下,call_user_func 也会进行类型检查
try {
call_user_func('expectInt', '42'); // TypeError
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}- 私有/受保护方法的限制
php
<?php
declare(strict_types=1);
class Example
{
private function privateMethod(): string
{
return 'private';
}
protected function protectedMethod(): string
{
return 'protected';
}
}
$ex = new Example();
// call_user_func 无法调用私有/受保护方法
// call_user_func([$ex, 'privateMethod']); // Fatal error
// call_user_func([$ex, 'protectedMethod']); // Fatal error最佳实践
1. 现代替代方案
php
<?php
declare(strict_types=1);
// 不推荐:call_user_func
$result = call_user_func('strtoupper', 'hello');
// 推荐:可变函数
$func = 'strtoupper';
$result = $func('hello');
// 推荐(PHP 8.1+):First-class callable
$result = strtoupper(...)('hello');
// 不推荐:call_user_func_array
$result = call_user_func_array('substr', ['hello world', 0, 5]);
// 推荐:参数展开
$args = ['hello world', 0, 5];
$result = substr(...$args);2. 使用 callable 类型声明替代字符串类型
php
<?php
declare(strict_types=1);
// 推荐:使用 callable 类型
function execute(callable $callback, mixed ...$args): mixed
{
return $callback(...$args);
}
// 不推荐:使用字符串类型再 call_user_func
function executeOld(string $functionName, array $args): mixed
{
return call_user_func_array($functionName, $args);
}3. 封装动态调用逻辑
php
<?php
declare(strict_types=1);
function safeCall(callable $callback, array $args = []): mixed
{
if (!is_callable($callback)) {
throw new InvalidArgumentException('Provided callback is not callable');
}
return call_user_func_array($callback, $args);
}
echo safeCall('strtoupper', ['hello']) . "\n"; // HELLO
echo safeCall(fn(int $a, int $b) => $a + $b, [3, 4]) . "\n"; // 7