Skip to content

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_funccall_user_func($func, ...$args)PHP 4+逐个传参
call_user_func_arraycall_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

注意事项

常见陷阱

  1. 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";
}
  1. 私有/受保护方法的限制
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

参考链接