数组解包 ...$arr
概述
PHP 7.4+ 引入了数组解包运算符 ...$arr(也称为展开运算符或 splat operator)。它可以在函数调用中和数组字面量中展开数组参数。本章详细讲解 ... 运算符的用法以及与 array_merge 的区别。
基础概念
| 用法 | 版本 | 语法 | 说明 |
|---|---|---|---|
| 函数调用展开 | PHP 5.6+ | func(...$arr) | 将数组元素作为参数传入 |
| 数组字面量展开 | PHP 7.4+ | [...$arr] | 合并数组 |
| 参数解包 | PHP 5.6+ | function func(...$args) | 可变参数 |
PHP 7.4+
数组字面量中的展开运算符 ... 需要 PHP 7.4+。函数调用中的展开 PHP 5.6+ 就支持。
语法与代码
函数调用中展开(PHP 5.6+)
php
<?php
declare(strict_types=1);
function add(int $a, int $b, int $c): int
{
return $a + $b + $c;
}
// 展开数组为函数参数
$numbers = [1, 2, 3];
echo add(...$numbers); // 6
// 等同于 add(1, 2, 3)
// 实际应用
$fruits = ['apple', 'banana', 'cherry'];
echo implode(', ', $fruits); // 常规方式
echo implode(', ', [...$fruits]); // 展开方式(效果相同)
// 与其他参数组合
function greet(string $prefix, string ...$names): string
{
return $prefix . ': ' . implode(', ', $names);
}
$nameList = ['Alice', 'Bob', 'Charlie'];
echo greet('Hello', ...$nameList);
// Hello: Alice, Bob, Charlie数组字面量中展开(PHP 7.4+)
php
<?php
declare(strict_types=1);
// 在数组字面量中使用展开运算符合并数组
$arr1 = [1, 2, 3];
$arr2 = [4, 5, 6];
$merged = [...$arr1, ...$arr2];
// [1, 2, 3, 4, 5, 6]
// 与其他元素混合
$combined = ['start', ...$arr1, 'middle', ...$arr2, 'end'];
// ['start', 1, 2, 3, 'middle', 4, 5, 6, 'end']
// 解包生成器
$gen = (function () {
yield 1;
yield 2;
yield 3;
})();
$arr = [...$gen];
// [1, 2, 3]与 array_merge 的区别
php
<?php
declare(strict_types=1);
// 关键区别:数字键的行为不同
$arr1 = [0 => 'a', 1 => 'b'];
$arr2 = [0 => 'c', 1 => 'd'];
// array_merge - 数字键重建索引
$merged = array_merge($arr1, $arr2);
// ['a', 'b', 'c', 'd'](索引 0,1,2,3)
// ... 展开运算符 - 数字键保持原样(后面的覆盖前面的)
$spread = [...$arr1, ...$arr2];
// ['c', 'd'](0 => 'c' 覆盖 0 => 'a',1 => 'd' 覆盖 1 => 'b')
// 对字符串键的行为相同(后面的覆盖前面的)
$assoc1 = ['key' => 'value1'];
$assoc2 = ['key' => 'value2'];
echo array_merge($assoc1, $assoc2)['key']; // value2
echo [...$assoc1, ...$assoc2]['key']; // value2
// + 运算符 - 保留第一个
echo ($assoc1 + $assoc2)['key']; // value1展开运算符的限制
php
<?php
declare(strict_types=1);
// 只能展开 integer 键的数组
$indexed = [1, 2, 3];
$assoc = ['a' => 1, 'b' => 2];
// 正确:展开索引数组
$result = [...$indexed]; // [1, 2, 3]
// PHP 8.1+ 允许展开字符串键数组
$assoc = ['a' => 1, 'b' => 2];
$result = [...$assoc]; // PHP 8.1+: ['a' => 1, 'b' => 2]
// PHP < 8.1: 字符串键会报 Fatal Error
// PHP 8.1+: 重复键时后面的覆盖前面的
$a = ['x' => 1, 'y' => 2];
$b = ['x' => 3, 'z' => 4];
$result = [...$a, ...$b];
// ['x' => 3, 'y' => 2, 'z' => 4]可变参数(Variadic)
php
<?php
declare(strict_types=1);
// 可变参数使用 ... 声明
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
echo sum(1, 2, 3); // 6
echo sum(...[1, 2, 3]); // 6(展开数组)
// 类型安全的可变参数
function concatenate(string ...$parts): string
{
return implode('', $parts);
}
echo concatenate('Hello', ' ', 'World'); // Hello World
// 可变参数 + 普通参数
function format(string $template, string ...$values): string
{
$i = 0;
return preg_replace_callback('/%s/',
fn() => $values[$i++] ?? '',
$template
);
}
echo format('Hello %s, you have %s messages', 'Alice', '5');详细说明
展开运算符的行为总结
| 场景 | ... 展开 | array_merge | + 运算符 |
|---|---|---|---|
| 数字键 | 后者覆盖前者 | 重建索引 | 保留前者 |
| 字符串键 | 后者覆盖前者 | 后者覆盖前者 | 保留前者 |
| 性能 | 快(单次操作) | 较慢(函数调用) | 快 |
选择建议
需要合并索引数组并重建索引时用 array_merge。需要合并并覆盖(保持索引)时用 ...。需要保留第一个值时用 +。
实战示例
中间件管道
php
<?php
declare(strict_types=1);
class Pipeline
{
public static function through(array $middlewares, callable $final): mixed
{
foreach (array_reverse($middlewares) as $middleware) {
$final = fn(): mixed => $middleware($final);
}
return $final();
}
}
$middlewares = [
fn(callable $next): mixed => $next(), // passthrough
fn(callable $next): mixed => $next(),
];
$result = Pipeline::through($middlewares, fn(): string => 'Done');
echo $result; // Done函数参数转发
php
<?php
declare(strict_types=1);
class HttpClient
{
public function get(string $url, array $options = []): string
{
return $this->request('GET', $url, ...$options);
}
public function request(string $method, string $url, string ...$options): string
{
// 处理请求
return "{$method} {$url}";
}
}
$client = new HttpClient();
echo $client->get('https://example.com', ['timeout' => '30']);展开运算符的高级用法
php
<?php
declare(strict_types=1);
// 展开运算符与 array_values
// 当需要重建索引数组时,展开比 array_values 更语义化
$filtered = array_filter([1, 2, 3, 4, 5], fn(int $n): bool => $n > 2);
$reindexed = [...$filtered];
// [3, 4, 5]
// 展开生成器(PHP 7.4+)
function numberRange(int $start, int $end): \Generator
{
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}
$arr = [...numberRange(1, 5)];
// [1, 2, 3, 4, 5]
// 合并多个集合
$set1 = new \Ds\Set([1, 2, 3]);
$set2 = new \Ds\Set([3, 4, 5]);
$combined = [...$set1->toArray(), ...$set2->toArray()];
// [1, 2, 3, 3, 4, 5](注意:Set 展开后可能有重复)
// 真正的集合并集
$union = new \Ds\Set([...$set1->toArray(), ...$set2->toArray()]);
// {1, 2, 3, 4, 5}可变参数与展开
php
<?php
declare(strict_types=1);
// 可变参数 vs 展开数组
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
// 直接传参
echo sum(1, 2, 3, 4, 5); // 15
// 展开数组传参
$numbers = [1, 2, 3, 4, 5];
echo sum(...$numbers); // 15
// 混合使用
function concat(string $sep, string ...$parts): string
{
return implode($sep, $parts);
}
echo concat(' - ', 'Hello', 'World', 'PHP');
// Hello - World - PHP
echo concat(' - ', ...['Hello', 'World', 'PHP']);
// Hello - World - PHP
// 展开多个数组
function merge(...$arrays): array
{
return array_merge(...$arrays);
}
echo json_encode(merge([1, 2], [3, 4], [5, 6]));
// [1, 2, 3, 4, 5, 6]展开运算符的性能对比
php
<?php
declare(strict_types=1);
// [...] vs array_merge vs +
$n = 10000;
$arr1 = range(1, $n);
$arr2 = range($n + 1, $n * 2);
// [...] 展开
$start = microtime(true);
$result = [...$arr1, ...$arr2];
$t1 = microtime(true) - $start;
// array_merge
$start = microtime(true);
$result = array_merge($arr1, $arr2);
$t2 = microtime(true) - $start;
// + 运算符
$start = microtime(true);
$result = $arr1 + $arr2;
$t3 = microtime(true) - $start;
// + 对于索引数组不是合并而是保留第一个,结果不同
// 展开和 array_merge 的性能大致相当
echo "展开: {$t1}s\n";
echo "array_merge: {$t2}s\n";PHP 8.1+ 字符串键展开
PHP 8.1 之前,... 只能展开整数键数组。PHP 8.1+ 支持展开字符串键数组,相同键时后者覆盖前者。
展开运算符的限制与解决方案
php
<?php
declare(strict_types=1);
// 字符串键数组展开(PHP 8.1+)
$a = ['x' => 1, 'y' => 2];
$b = ['x' => 3, 'z' => 4];
$result = [...$a, ...$b];
// ['x' => 3, 'y' => 2, 'z' => 4](重复键取后者)
// PHP < 8.1 的替代方案:使用 array_merge
$result = array_merge($a, $b); // 同样的结果
// 注意:展开运算符展开 SplFixedArray
$fixed = new SplFixedArray(3);
$fixed[0] = 'a';
$fixed[1] = 'b';
$fixed[2] = 'c';
$arr = [...$fixed]; // PHP 8.0+: ['a', 'b', 'c']
// 展开多个可迭代对象
function mergeAll(iterable ...$iterables): array
{
$result = [];
foreach ($iterables as $iterable) {
foreach ($iterable as $item) {
$result[] = $item;
}
}
return $result;
}注意事项
PHP 8.1 前
PHP 8.0 及之前版本,... 只能展开整数键数组。展开字符串键数组会报 Fatal Error。
展开大数组
展开非常大的数组(100万+ 元素)可能导致内存问题。考虑使用生成器或逐元素处理。
最佳实践
- 合并索引数组用 array_merge:需要重建索引
- 合并关联数组用 ... 或 +:根据覆盖需求选择
- 函数参数展开用 ...:替代
call_user_func_array - 可变参数用 ...$args:替代
func_get_args() - PHP 8.1+ 关联展开:注意重复键的覆盖行为