可变参数
概述
可变参数(Variadic Functions)允许函数接受任意数量的参数。PHP 5.6 引入了 ... 运算符来声明可变参数,取代了传统的 func_get_args() 系列函数。可变参数使函数更加灵活,能够处理不确定数量的输入。
PHP 版本说明
...运算符自 PHP 5.6 引入func_get_args()/func_num_args()/func_get_arg()自 PHP 8.0 起与可变参数类型声明不兼容,不推荐使用- 可变参数可以添加类型声明,自 PHP 7.0 起支持标量类型
基础概念
什么是可变参数
可变参数函数可以接受零个或多个参数。在函数定义中,可变参数使用 ... 前缀标识,PHP 会将传入的所有额外参数收集为一个数组。
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
echo sum(1, 2, 3); // 6
echo sum(1, 2, 3, 4); // 10
echo sum(); // 0新旧方式对比
| 特性 | ... 运算符(推荐) | func_get_args()(旧式) |
|---|---|---|
| 引入版本 | PHP 5.6+ | PHP 4+ |
| 语法 | ...$params | func_get_args() |
| 类型声明 | 支持 | 不支持 |
| 与严格模式兼容 | 是 | 否(有警告) |
| 可读性 | 好 | 差 |
| 性能 | 更优 | 略差 |
语法与代码
基本 ... 运算符语法
<?php
declare(strict_types=1);
// 基本可变参数
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
echo sum(1, 2, 3, 4, 5) . "\n"; // 15
echo sum(10, 20) . "\n"; // 30
echo sum() . "\n"; // 0
// 可变参数在函数内部就是数组
function showNumbers(int ...$numbers): void
{
echo "参数个数: " . count($numbers) . "\n";
echo "参数列表: " . implode(', ', $numbers) . "\n";
}
showNumbers(1, 2, 3);
// 参数个数: 3
// 参数列表: 1, 2, 3可变参数与固定参数混合
可变参数可以与固定参数一起使用,但可变参数必须放在参数列表的最后。
<?php
declare(strict_types=1);
// 固定参数 + 可变参数
function format(string $template, string ...$values): string
{
$i = 0;
return preg_replace_callback(
'/\{(\d+)\}/',
fn($match) => $values[$match[1]] ?? $match[0],
$template
);
}
echo format('Hello {0}, you have {1} new messages', 'Alice', 3) . "\n";
// Hello Alice, you have 3 new messages
// 多个固定参数 + 可变参数
function buildUrl(
string $baseUrl,
string $path,
array $query = [],
string ...$segments
): string {
$url = rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
if (!empty($segments)) {
$url .= '/' . implode('/', array_map('rawurlencode', $segments));
}
if (!empty($query)) {
$queryString = http_build_query($query);
$url .= '?' . $queryString;
}
return $url;
}
echo buildUrl(
'https://api.example.com',
'search',
['q' => 'php', 'page' => 1],
'tutorials', 'variadic'
) . "\n";
// https://api.example.com/search/tutorials/variadic?q=php&page=1位置限制
可变参数必须是参数列表的最后一个参数。以下定义会导致语法错误:
// 错误
function test(int ...$numbers, string $name) {}类型声明 + 可变参数
为可变参数添加类型声明后,传入的每个参数都必须符合该类型。
<?php
declare(strict_types=1);
// 所有参数都必须是 int 类型
function sumInts(int ...$numbers): int
{
return array_sum($numbers);
}
echo sumInts(1, 2, 3) . "\n"; // 6
// sumInts(1, '2', 3); // TypeError in strict mode
// 所有参数都必须是 string 类型
function joinStrings(string ...$parts): string
{
return implode(' - ', $parts);
}
echo joinStrings('PHP', 'MySQL', 'Redis') . "\n"; // PHP - MySQL - Redis
// 使用联合类型的可变参数
function processValues(int|float|string ...$values): array
{
return array_map(fn($v) => get_debug_type($v) . ':' . $v, $values);
}
print_r(processValues(1, 2.5, 'hello', 42));
// Array ( [0] => int:1 [1] => double:2.5 [2] => string:hello [3] => int:42 )旧方式:func_get_args 系列函数
在 PHP 5.6 之前,使用 func_get_args()、func_num_args() 和 func_get_arg() 获取可变参数。这些函数在现代 PHP 中仍然可用,但不推荐使用。
<?php
declare(strict_types=1);
// 旧方式(不推荐,在 PHP 8.0+ 中会产生编译警告)
function oldSum()
{
$args = func_get_args(); // 获取所有参数的数组
$count = func_num_args(); // 获取参数个数
$first = func_get_arg(0); // 获取指定位置的参数
$total = 0;
for ($i = 0; $i < $count; $i++) {
$total += func_get_arg($i);
}
echo "Count: {$count}, First: {$first}, Total: {$total}\n";
}
oldSum(1, 2, 3, 4, 5);
// Count: 5, First: 1, Total: 15
// 新方式(推荐)
function newSum(int ...$numbers): int
{
echo "Count: " . count($numbers) . "\n";
echo "First: " . ($numbers[0] ?? 'none') . "\n";
return array_sum($numbers);
}
newSum(1, 2, 3, 4, 5);
// Count: 5, First: 1, 返回: 15PHP 8.0+ 兼容性
在 PHP 8.0+ 中,如果函数使用了可变参数 ...$params,则在同一函数中调用 func_get_args() 等函数会产生编译错误。两者不能在同一函数中混用。
详细说明
可变参数的内部机制
当使用 ... 运算符时,PHP 会将传入的参数打包为一个数组。这个过程是透明的,不会引入额外的性能开销。
<?php
declare(strict_types=1);
function analyze(int ...$numbers): void
{
echo "类型: " . gettype($numbers) . "\n"; // array
echo "是否数组: " . (is_array($numbers) ? '是' : '否') . "\n"; // 是
echo "元素数量: " . count($numbers) . "\n";
// 可以像普通数组一样操作
echo "第一个: " . $numbers[0] . "\n";
echo "最后一个: " . end($numbers) . "\n";
echo "最大值: " . max($numbers) . "\n";
echo "最小值: " . min($numbers) . "\n";
echo "平均值: " . (array_sum($numbers) / count($numbers)) . "\n";
}
analyze(10, 20, 30, 40, 50);可变参数的空值情况
当不传入任何参数时,可变参数变量为空数组,而不是 null。
<?php
declare(strict_types=1);
function handleVariadic(string ...$items): void
{
if (empty($items)) {
echo "没有传入任何参数\n";
return;
}
foreach ($items as $item) {
echo "- {$item}\n";
}
}
handleVariadic(); // 没有传入任何参数
handleVariadic('item1', 'item2'); // - item1 \n - item2可变参数与引用传递
从 PHP 5.6 开始,可变参数可以声明为引用传递(PHP 8.0+ 更推荐使用不可变数据结构)。
<?php
declare(strict_types=1);
function modifyAll(int &...$numbers): void
{
for ($i = 0; $i < count($numbers); $i++) {
$numbers[$i] *= 2;
}
}
$values = [1, 2, 3, 4];
modifyAll(...$values);
print_r($values);
// Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 )引用可变参数
引用可变参数要求使用参数展开(...)来调用,不能直接传递值。如果数组中的元素是字面量,将会报错。
实战示例
通用数据聚合器
<?php
declare(strict_types=1);
function aggregate(string $operation, int|float ...$numbers): float
{
return match ($operation) {
'sum' => array_sum($numbers),
'avg' => count($numbers) > 0 ? array_sum($numbers) / count($numbers) : 0.0,
'max' => max($numbers),
'min' => min($numbers),
'product' => array_product($numbers),
default => throw new InvalidArgumentException("Unknown operation: {$operation}"),
};
}
echo aggregate('sum', 1, 2, 3, 4, 5) . "\n"; // 15
echo aggregate('avg', 10, 20, 30) . "\n"; // 20
echo aggregate('max', 3, 7, 1, 9, 4) . "\n"; // 9
echo aggregate('product', 2, 3, 4) . "\n"; // 24日志记录器
<?php
declare(strict_types=1);
function logMessage(string $level, string $message, mixed ...$context): void
{
$timestamp = date('Y-m-d H:i:s');
$prefix = strtoupper($level);
$line = "[{$timestamp}] [{$prefix}] {$message}";
if (!empty($context)) {
$line .= ' | Context: ' . json_encode($context, JSON_UNESCAPED_UNICODE);
}
echo $line . "\n";
}
logMessage('info', 'Application started');
// [2024-01-15 10:00:00] [INFO] Application started
logMessage('error', 'Database connection failed', [
'host' => 'localhost',
'port' => 3306,
'error' => 'Connection refused',
]);
// [2024-01-15 10:00:00] [ERROR] Database connection failed | Context: {"host":"localhost","port":3306,"error":"Connection refused"}批量数据处理管道
<?php
declare(strict_types=1);
function pipeline(mixed $data, callable ...$stages): mixed
{
foreach ($stages as $stage) {
$data = $stage($data);
}
return $data;
}
// 定义处理阶段
$trimStage = fn(string $s) => trim($s);
$lowerStage = fn(string $s) => strtolower($s);
$slugStage = fn(string $s) => preg_replace('/[^a-z0-9]+/', '-', $s);
$trimSlug = fn(string $s) => trim($s, '-');
// 通过管道处理数据
$title = " Hello WORLD! This is a Test ";
$slug = pipeline($title, $trimStage, $lowerStage, $slugStage, $trimSlug);
echo $slug . "\n";
// hello-world-this-is-a-test
// 数值处理管道
$numbers = [3, 1, 4, 1, 5, 9, 2, 6];
$result = pipeline(
$numbers,
fn(array $arr) => array_filter($arr, fn($n) => $n > 2),
fn(array $arr) => array_values($arr),
fn(array $arr) => array_map(fn($n) => $n * 2, $arr),
fn(array $arr) => array_sum($arr),
);
echo $result . "\n"; // 68 (3*2 + 4*2 + 5*2 + 9*2 + 6*2)注意事项
性能考量
可变参数在内部实现为数组打包,对于大量参数的场景,直接传递数组可能更高效。
<?php
declare(strict_types=1);
// 可变参数:每次调用都需要打包参数
function sumVariadic(int ...$numbers): int
{
return array_sum($numbers);
}
// 直接接受数组:对于已知大量数据的情况更高效
function sumArray(array $numbers): int
{
return array_sum($numbers);
}
$data = range(1, 1000);
// 推荐:数据量大时直接传递数组
echo sumArray($data) . "\n"; // 500500
// 可变参数适合参数个数较少且不确定的场景
echo sumVariadic(1, 2, 3, 4, 5) . "\n"; // 15可变参数与默认参数
可变参数可以与有默认值的固定参数组合使用,但可变参数始终在最后。
<?php
declare(strict_types=1);
function buildSelect(
string $table,
string $alias = 't',
string ...$columns
): string {
$cols = !empty($columns) ? implode(', ', $columns) : '*';
return "SELECT {$cols} FROM {$table} AS {$alias}";
}
echo buildSelect('users') . "\n";
// SELECT * FROM users AS t
echo buildSelect('users', 'u', 'id', 'name', 'email') . "\n";
// SELECT id, name, email FROM users AS u最佳实践
1. 优先使用 ... 运算符
<?php
declare(strict_types=1);
// 推荐:现代可变参数
function processItems(string ...$items): void
{
foreach ($items as $item) {
echo "- {$item}\n";
}
}
// 不推荐:旧方式
function processItemsOld(): void
{
foreach (func_get_args() as $item) {
echo "- {$item}\n";
}
}2. 为可变参数添加类型声明
<?php
declare(strict_types=1);
// 推荐:有类型声明
function calculateAverage(int|float ...$values): float
{
return count($values) > 0 ? array_sum($values) / count($values) : 0.0;
}
// 不推荐:无类型声明
function calculateAverageNoType(...$values)
{
return count($values) > 0 ? array_sum($values) / count($values) : 0;
}3. 处理空参数场景
<?php
declare(strict_types=1);
function mergeConfigs(array ...$configs): array
{
if (empty($configs)) {
return [];
}
return array_merge(...$configs);
}
$default = ['debug' => false, 'cache' => true];
$env = ['debug' => true];
$user = ['theme' => 'dark'];
$merged = mergeConfigs($default, $env, $user);
print_r($merged);
// Array ( [debug] => true [cache] => true [theme] => dark )4. 避免过多可变参数
如果可变参数可能非常大,建议直接传递数组,以避免参数栈溢出或性能问题。