Skip to content

func_get_args / func_num_args / func_get_arg

概述

func_get_args()func_num_args()func_get_arg() 是 PHP 中获取函数参数信息的三个传统函数。它们可以在函数内部获取所有传入的参数列表、参数个数以及指定位置的参数值,无需在函数签名中声明参数。自 PHP 5.6 引入 ... 可变参数运算符后,这些函数已不再推荐使用。

PHP 版本说明

  • 这些函数自 PHP 4 起可用
  • PHP 5.6 引入 ... 运算符作为现代替代
  • PHP 8.0 中,如果函数同时使用了 ... 可变参数和 func_get_args(),会产生编译错误
  • 在严格模式下,这些函数的行为可能有细微差别

基础概念

三个函数对比

函数功能返回值
func_get_args()获取所有参数的数组array
func_num_args()获取参数个数int
func_get_arg(int $pos)获取指定位置的参数mixed

不推荐使用

在 PHP 8.0+ 环境中,推荐使用 ... 可变参数运算符替代这些函数。这些函数在未来版本中可能被废弃。

语法与代码

基本用法

php
<?php

declare(strict_types=1);

// func_num_args:获取参数个数
function showCount(): void
{
    echo "参数个数: " . func_num_args() . "\n";
}

showCount();              // 参数个数: 0
showCount('a');           // 参数个数: 1
showCount('a', 'b', 'c'); // 参数个数: 3

// func_get_args:获取所有参数的数组
function showAllArgs(): void
{
    $args = func_get_args();
    echo "所有参数: " . implode(', ', $args) . "\n";
}

showAllArgs('hello', 42, true); // 所有参数: hello, 42, 1

// func_get_arg:获取指定位置的参数
function showArgAt(int $position): void
{
    echo "参数[{$position}]: " . func_get_arg($position) . "\n";
}

showArgAt(0, 'first', 'second', 'third'); // 参数[0]: first
showArgAt(1, 'first', 'second', 'third'); // 参数[1]: second

计算任意数量参数的和

php
<?php

declare(strict_types=1);

// 旧方式:使用 func_get_args
function sumOld(): int
{
    $args = func_get_args();
    $total = 0;

    foreach ($args as $arg) {
        $total += $arg;
    }

    return $total;
}

echo sumOld(1, 2, 3, 4, 5) . "\n"; // 15
echo sumOld() . "\n";                // 0

// 新方式:使用 ... 运算符(PHP 5.6+)
function sumNew(int ...$numbers): int
{
    return array_sum($numbers);
}

echo sumNew(1, 2, 3, 4, 5) . "\n"; // 15
echo sumNew() . "\n";                // 0

带固定参数的可变参数

php
<?php

declare(strict_types=1);

// 旧方式:固定参数 + func_get_args
function formatOld(string $separator): string
{
    $args = func_get_args();
    // func_get_args 包含所有参数,包括固定参数
    array_shift($args); // 移除第一个固定参数 $separator

    return implode($separator, $args);
}

echo formatOld('-', 'a', 'b', 'c') . "\n"; // a-b-c

// 新方式:使用 ... 可变参数
function formatNew(string $separator, string ...$items): string
{
    return implode($separator, $items);
}

echo formatNew('-', 'a', 'b', 'c') . "\n"; // a-b-c

参数转发

php
<?php

declare(strict_types=1);

// 旧方式:转发参数
function logOld(string $message): void
{
    $args = func_get_args();
    array_shift($args); // 移除 $message

    $context = !empty($args) ? $args : [];
    echo "[LOG] {$message} | " . json_encode($context) . "\n";
}

logOld('User login', ['userId' => 42, 'ip' => '127.0.0.1']);

// 新方式:使用 ... 可变参数
function logNew(string $message, mixed ...$context): void
{
    echo "[LOG] {$message} | " . json_encode($context) . "\n";
}

logNew('User login', ['userId' => 42, 'ip' => '127.0.0.1']);

详细说明

与可变参数的兼容性问题

PHP 8.0+ 中,func_get_args() 等函数不能与可变参数声明 ...$params 在同一函数中使用。

php
<?php

declare(strict_types=1);

// PHP 8.0+ 中这会产生编译错误
// function mixed(int $a, int ...$numbers): int {
//     $allArgs = func_get_args(); // Error!
//     return array_sum($allArgs);
// }

// 解决方案 1:只使用 ... 可变参数
function sumAll(int ...$numbers): int
{
    return array_sum($numbers);
}

// 解决方案 2:只使用 func_get_args(不声明可变参数)
function sumAllOld()
{
    return array_sum(func_get_args());
}

在严格模式下的行为

php
<?php

declare(strict_types=1);

// func_get_args 不受类型声明影响
// 它返回的是调用时传入的原始值

function strictFunc(int $a): void
{
    // 即使有类型声明,func_get_args 返回原始值
    $args = func_get_args();

    foreach ($args as $i => $arg) {
        echo "Arg[{$i}]: " . get_debug_type($arg) . " = " . var_export($arg, true) . "\n";
    }
}

strictFunc(42);
// Arg[0]: int = 42

// 在非严格模式下传入 '42'
// func_get_args 仍然返回字符串 '42',而不是转换后的整数 42

func_get_arg 的边界检查

php
<?php

declare(strict_types=1);

function testArgs(): void
{
    $count = func_num_args();

    for ($i = 0; $i < $count + 2; $i++) {
        try {
            $arg = func_get_arg($i);
            echo "Arg[{$i}]: " . var_export($arg, true) . "\n";
        } catch (ArgumentCountError $e) {
            echo "Arg[{$i}]: " . $e->getMessage() . "\n";
        }
    }
}

testArgs('a', 'b');
// Arg[0]: 'a'
// Arg[1]: 'b'
// Arg[2]: func_get_arg(): Argument #2 ($position) must be less than the number of arguments

实战示例

兼容旧代码的参数处理

php
<?php

declare(strict_types=1);

// 旧式函数,需要在没有 ... 运算符的情况下处理可变参数
function legacyWrapper(): mixed
{
    $callback = array_shift($args = func_get_args());

    if (!is_callable($callback)) {
        throw new InvalidArgumentException('First argument must be callable');
    }

    return call_user_func_array($callback, $args);
}

// 兼容旧代码的调用
$result = legacyWrapper('strtoupper', 'hello');
echo $result . "\n"; // HELLO

$result = legacyWrapper('array_sum', [1, 2, 3, 4, 5]);
echo $result . "\n"; // 15

日志包装器

php
<?php

declare(strict_types=1);

class Logger
{
    private string $logFile;

    public function __construct(string $logFile)
    {
        $this->logFile = $logFile;
    }

    public function log(string $level, string $message): void
    {
        $args = func_get_args();
        array_shift($args); // 移除 $level
        array_shift($args); // 移除 $message

        $timestamp = date('Y-m-d H:i:s');
        $context = !empty($args) ? ' | ' . json_encode($args) : '';
        $line = "[{$timestamp}] [{$level}] {$message}{$context}\n";

        echo $line; // 或 file_put_contents
    }
}

$logger = new Logger('/tmp/app.log');
$logger->log('INFO', 'Application started');
$logger->log('ERROR', 'Database error', ['code' => 1045, 'host' => 'localhost']);
$logger->log('DEBUG', 'Query executed', 'SELECT * FROM users', 0.05);

注意事项

常见陷阱

  1. func_get_args 包含所有参数
php
<?php

declare(strict_types=1);

function test(int $fixed, int $optional = 0): void
{
    $args = func_get_args();
    // func_get_args 包含 $fixed 和 $optional,以及任何额外参数
    echo "Count: " . count($args) . "\n";
    print_r($args);
}

test(1, 2, 3, 4);
// Count: 4
// Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 )
  1. 在引用参数中使用
php
<?php

declare(strict_types=1);

function testRef(int &$ref): void
{
    // func_get_args 返回的是值的副本,不是引用
    $args = func_get_args();
    $args[0] = 999;

    echo "ref = {$ref}\n"; // 不会改变(只修改了副本)
}
  1. PHP 8.0+ 编译错误
php
<?php

declare(strict_types=1);

// 以下代码在 PHP 8.0+ 中会报错
// function mixed(int $required, ...$optional) {
//     func_get_args(); // Cannot use func_get_args() in a function with variadic parameters
// }

最佳实践

1. 使用可变参数替代

php
<?php

declare(strict_types=1);

// 不推荐(旧方式)
function processOld()
{
    $args = func_get_args();
    foreach ($args as $arg) {
        echo $arg . "\n";
    }
}

// 推荐(新方式)
function processNew(mixed ...$args): void
{
    foreach ($args as $arg) {
        echo $arg . "\n";
    }
}

2. 如果维护旧代码,添加类型安全

php
<?php

declare(strict_types=1);

// 为旧式函数添加运行时类型检查
function typedSum(): int
{
    $args = func_get_args();

    foreach ($args as $i => $arg) {
        if (!is_int($arg) && !is_float($arg)) {
            throw new TypeError("Argument #{$i} must be int or float");
        }
    }

    return array_sum($args);
}

echo typedSum(1, 2, 3) . "\n"; // 6

try {
    typedSum(1, 'two', 3);
} catch (TypeError $e) {
    echo $e->getMessage() . "\n";
}

3. 重构建议

将使用 func_get_args 的旧函数重构为使用 ... 运算符的现代写法。

php
<?php

declare(strict_types=1);

// 重构前
function queryOld()
{
    $sql = func_get_arg(0);
    $params = array_slice(func_get_args(), 1);

    foreach ($params as $param) {
        $sql = str_replace_first('?', (string) $param, $sql);
    }

    return $sql;
}

// 重构后
function queryNew(string $sql, string|int|float ...$params): string
{
    foreach ($params as $param) {
        $sql = preg_replace('/\?/', (string) $param, $sql, 1);
    }

    return $sql;
}

参考链接