PHP中三个点(...)的含义是什么?

238
在我安装Magento 2到我的服务器时,出现了一个错误。经过调查代码,发现有三个点(...),这是导致错误的原因。下面是我找到的代码:
return new $type(...array_values($args));

这个运算符叫什么,它的目的是什么?

PHP 5.6 引入了数组打包/解包。 - Mark Baker
你在安装之前有检查过需求吗:http://devdocs.magento.com/guides/v2.0/install-gde/system-requirements-tech.html - Mathieu de Lorimier
1
请参考:PHP扩展运算符 - dreftymac
另外,PHP8允许在函数/方法调用中将关联数组解包为命名参数。请参见:https://dev59.com/SXM_5IYBdhLWcg3whznx#64997399。 - mickmackusa
1
你解决了你遇到的错误吗?我在 Magento 2 网站上升级到 PHP 8.1 和 Magento 2.4.4 后,在代码的确切行上遇到了一个错误。 - johnsnails
9个回答

327

这个在 PHP 中被称为...运算符,而在其他语言中则称为展开操作符。来自2014年LornaJane博客文章对该特性的描述如下:

This feature allows you to capture a variable number of arguments to a function, combined with "normal" arguments passed in if you like. It's easiest to see with an example:

function concatenate($transform, ...$strings) {
    $string = '';
    foreach($strings as $piece) {
       $string .= $piece;
    }
    return($transform($string));  
 }

echo concatenate("strtoupper", "I'd ", "like ", 4 + 2, " apples");
(This would print I'D LIKE 6 APPLES)
在函数声明的参数列表中有...运算符,它基本上意味着“...其他所有内容应该放入$strings中”。您可以将2个或更多参数传递到此函数中,第二个及其后续参数将被添加到$strings 数组中,准备好使用。

3
谢谢 :),我为什么要使用SPLAT运算符,而不是将所有这些字符串作为第二个参数传递到数组中呢? - bikash.bilz
3
@bikash.bilz 我来回答:我认为这只是“语法糖”。展开运算符可以避免你用 [] 包围参数。虽然它并没有太多的好处,但我认为它看起来很不错。 - Cave Johnson
48
在PHP 7.2中,你可以对可变参数进行类型提示。因此,你可以定义function myFunc($foo, string ...$bar)。然后$bar将为你的函数提供一个字符串数组,并且只有字符串数组,在运行时得到保证。你不能用单个数组参数来实现这一点。 - Jason
7
这不仅仅是语法糖,因为它允许在原本需要固定字符串数量的上下文中使用数组。使用固定数量的字符串会导致您每次字符串数量可能变化时都需要重构源代码。 - dreftymac
5
一个简单的例子是函数签名用于查询数据库。如果你想要除了这三个字段之外的其他字段,function get_data($fname,$lname,$age) 将不得不进行更改。而 function get_data(...$fields) 不需要更改,你只需要在 $fields 中指定你想要的字段。@heykatieben - dreftymac
显示剩余4条评论

103

有三种用途可以使用省略符(...) PHP标记

可以将前两种用途看作是对数组进行打包解包。这些用途适用于函数参数(函数定义)和参数(函数执行)。

而第三种用途,一流可调用语法,在PHP 8.1.0中引入,有另外一种用法——从现有函数创建匿名函数。在这种情况下,运算符被用于替代函数参数。


打包

在定义函数时,如果你需要在函数中提供动态数量的变量(即,当调用函数时,你不知道会提供多少参数给该函数),可以在变量名前加上省略号 (...) 标记,例如 ...$numbers,将所有(剩余的)参数捕获到一个数组中,并将该数组赋值给命名变量(在本例中为$numbers),该变量可以在函数块内访问。通过前缀省略号 (...) 捕获的参数数量可以是零个或多个。

例如

// function definition
function sum (...$numbers) { // use ellipsis token when defining function
    $acc = 0;
    foreach ($numbers as $nn) {
        $acc += $nn;
    }
    return $acc;
}

// call the function
echo sum(1, 2, 3, 4); // provide any number of arguments

> 10

// and again...
echo sum(1, 2, 3, 4, 5);

> 15

// and again...
echo sum();

> 0

当在编写新函数时使用packing,给变量名前加上省略号(...)可以捕获所有剩余的参数,也就是说,你仍然可以有任意数量的初始、固定(位置)参数:
function sum ($first, $second, ...$remaining_numbers) {
    $acc = $first + $second;
    foreach ($remaining_numbers as $nn) {
        $acc += $nn;
    }
    return $acc;
}

// call the function
echo sum(1, 2); // provide at least two arguments

> 3

// and again...
echo sum(1, 2, 3, 4); // first two are assigned to fixed arguments, the rest get "packed"

> 10

...前缀省略号变量捕获了其余的所有内容。因此,它必须是最后一个函数参数。

解包

或者,当调用一个函数时,如果你提供给该函数的参数之前已经组合成一个数组,可以使用一个以省略号(...)为前缀的变量"inline"将该数组变量转换为作为函数参数提供的单独的参数。当任意数量的函数参数被替换为以省略号为前缀的变量时,每个数组元素都被分配给函数定义中命名的相应函数参数变量。

例如:

function add ($aa, $bb, $cc) {
    return $aa + $bb + $cc;
}

$arr = [1, 2, 3];
echo add(...$arr); // use ellipsis token when calling function

> 6

$first = 1;
$arr = [2, 3];
echo add($first, ...$arr); // used with positional arguments

> 6

$first = 1;
$arr = [2, 3, 4, 5]; // array can be "oversized"
echo add($first, ...$arr); // remaining elements are ignored

> 6

解包在使用数组函数操作数组或变量时特别有用。
例如,解包array_slice的结果:
function echoTwo ($one, $two) {
    echo "$one\n$two";
}

$steaks = array('ribeye', 'kc strip', 't-bone', 'sirloin', 'chuck');

// array_slice returns an array, but ellipsis unpacks it into function arguments
echoTwo(...array_slice($steaks, -2)); // return last two elements in array

> sirloin
> chuck

一流的可调用函数

从 PHP 8.1.0 开始,当省略号 (...) 代替函数参数时,例如 strlen(...),它是一种一流的可调用函数语法,这意味着它可以从一个可调用对象,也就是一个现有的函数中创建一个匿名函数。

$sl = strlen(...); // create an anonymous function from an internal function
echo $sl('fun stuff'); // execute it

> 9

// a user-defined function
function test($hey, $there) {
  echo $hey . $there;
}

$ya = test(...); // create an anonymous function
$ya('oopsy', 'daisy'); // execute it

> oopsydaisy

Unpack 对于 Doctrine 的 OrX 函数非常有用。 - Erdal G.

29
每个答案都指向同一篇博客文章,此外,在这里是关于可变长度参数列表的官方文档:http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list 在 PHP 5.6 及更高版本中,参数列表可以包括“...”符号来表示函数接受可变数量的参数。参数将作为数组传递给给定变量。
似乎“splat”操作符不是官方名称,但它很可爱!

17

PHP 7.4中,省略号也是扩展运算符

$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// ['banana', 'orange', 'apple', 'pear', 'watermelon'];

来源:https://wiki.php.net/rfc/spread_operator_for_array


17

意思是将一个关联数组分解为列表。因此,您无需输入N个参数来调用方法,只需一个即可。如果方法允许分解参数,并且参数类型相同。

对我来说,展开运算符最重要的一点是它可以帮助键入数组参数:

$items = [
    new Item(), 
    new Item()
];

$collection = new ItemCollection();
$collection->add(...$items); // !

// what works as well:
// $collection->add(new Item());
// $collection->add(new Item(), new Item(), new Item()); // :(

class Item  {};
 
class ItemCollection {
    
    /**
     * @var Item[]
     */
    protected $items = [];
    
    public function add(Item ...$items)
    {
        foreach ($items as &$item) {
            $this->items[] = $item;
        }
    }
} 

它可以节省一些类型控制的工作量,尤其是在处理大型集合或非常面向对象的时候。

需要注意的重要事项是,...$array会分解数组,而不考虑其项目的类型,因此你也可以走比较丑陋的方式:

function test(string $a, int $i) {
    echo sprintf('%s way as well', $a);
    
    if ($i === 1) {
        echo('!');
    }
}

$params = [
    (string) 'Ugly',
    (int) 1
];

test(...$params);

// Output:
// Ugly way as well!

但请不要这样做。


PHP 8 更新 - 命名参数

自从 PHP 8 版本起,你现在可以分解关联数组,也就是带有键和值的数组。如果你在一个函数中使用具有相同命名参数的关联数组,PHP 将会把值传递到正确的变量中:

$arr = [
  'pi' => 3.14,
  'r' => 4,
];

function circ($r, $pi) {
    return 2*$pi*$r;
}

// so that call
circ(...$arr);

// will be effectively a call of
circ(pi: 3.14, r: 4);

而且你可以稍微不太担心参数的顺序。


PHP 8.1更新 - 创建可调用的新语法

尽管与数组一起使用,... 赢得了全新、非常有用的功能,可以从任何上下文中创建可调用:

$f = strtoupper(...); // creating a callable
echo $f('fo');

class test {
    public function func($a) {
        echo $a . PHP_EOL;
    }
}

$f = (new test)
         ->func(...); // creating a callable

$f('x');

这并不丑陋。就像Doctrine的OrX函数需要一个列表,但你必须传递一个数组(因为你不知道要传递多少个元素)。我发现这种方式比使用call_user_func_array更好。 - Erdal G.
@ErdalG. 这是一个由一种条件对象组成的数组,我猜没错吧? - yergo
老实说,我不记得3年前脑子里的例子了哈哈(我应该还是写下来比较好)。它可能是一些随机生成表达式的任意数组。对于唯一条件的数组,你可以显然地用 ->in() 或类似的方法进行替换。 - Erdal G.
“echo sprintf()”是一种反模式。每当您认为脚本需要它时,请不要使用它,只需键入“printf()”。 - mickmackusa

15

8
好的,我会尽力为您翻译。以下是需要翻译的内容:This is not the answer to the author's original question, but it may be relevant to anyone who googles 'php 3 dots'这不是作者原问题的答案,但对于任何Google“php 3个点”的人可能是相关的。 - michal3377
现在,它在文档中:https://www.php.net/manual/zh/functions.first-class-callable-syntax.php - ssi-anik
而且,如果想要检查用法:https://github.com/symfony/console/blob/17524a64ebcfab68d237bbed247e9a9917747096/Question/Question.php#L185 - ssi-anik
@michal3377 作者最初的问题是关于...运算符及其用途。尽管作者最初在这个上下文中没有遇到它,但这个运算符具有相同的名称和附加的用途,因此从技术上讲,作者也问到了这一点,即使他们当时没有意识到。 - apokryfos

6
要使用此功能,只需使用...运算符告诉PHP需要将数组解包为变量。有关更多详细信息,请参见此处,一个简单的示例可能如下所示:
$email[] = "Hi there";
$email[] = "Thanks for registering, hope you like it";

mail("someone@example.com", ...$email);

4
这是所谓的“展开”运算符。基本上,这个东西可以翻译为“任意数量的参数”,是在PHP 5.6中引入的。
有关进一步详情,请参见此处

1

似乎没有人提到过,所以在这里说明一下[这也有助于Google(及其他搜索引擎)指导开发人员提出关于PHP中Rest Parameters的问题]:

如此指示,它在JS中被称为Rest Parameters,我更喜欢这个有意义的命名方式而不是那个“splat thing”!

在PHP中,由...args提供的功能被称为可变函数,它是在PHP5.6中引入的。相同的功能曾经使用func_get_args()来实现。

为了正确使用它,你应该在任何可以帮助减少样板代码的地方使用rest parameters语法。


1
除了现有的答案之外,这并没有提供任何有用的东西。 - TylerH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接