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',而不是转换后的整数 42func_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);注意事项
常见陷阱
- 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 )- 在引用参数中使用
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"; // 不会改变(只修改了副本)
}- 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;
}