PHP の ... (3点ドット, Three Dots) の種類，全部言えるかな？

下記で使われている `...` に関して
  - 🔰 シンタックスは，そもそも有効でしょうか？
  - 🔰 有効である場合，どういう役目を持っているでしょうか？  
  - 🎓 有効である場合，どの PHP バージョン以降で有効でしょうか？

:::note warn
ヒント: 1つだけ，エラーになるものが含まれています！
:::

## Q1

```php
<?php

$nested = [
    ['foo'],
    ['bar', 'baz'],
    ['qux'],
];
                         // ↓
$collapsed = array_merge(...$nested);

var_dump($collapsed);
```

<details>
<summary>回答</summary>

:::note success
**引数のアンパック** です。 PHP 5.6 以降で使用可能です。

- [... による引数のアンパック ¶](https://www.php.net/manual/ja/migration56.new-features.php#migration56.new-features.splat)
:::

:::note warn
PHP **7.3** 以前では， `array_merge()` の引数が空になることを考慮しなければなりません。

```php
$collapsed = array_merge([], ...$nested); // PHP 7.3 以前ではこう書く
```

- [引数を渡さずに array_merge 系の関数を使用する ¶](https://www.php.net/manual/ja/migration74.new-features.php#migration74.new-features.standard.array-merge-no-args)
:::

<details>
<summary>出力</summary>
ネストを一段アンラップしたものがマージされます。

```
array(4) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  string(3) "baz"
  [3]=>
  string(3) "qux"
}
```
</details>
</details>

## Q2

```php
<?php
                       // ↓
function writeLine(string ...$lines): void
{
    foreach ($lines as $line) {
        echo "$line\n";
    }
}

writeLine("foo");
writeLine("bar", "baz");
```

<details>
<summary>回答</summary>

:::note success
**可変長引数** です。 PHP 5.6 以降で使用可能です。

- [... による可変個引数関数 ¶](https://www.php.net/manual/ja/migration56.new-features.php#migration56.new-features.variadics)
:::
<details>
<summary>出力</summary>

```
foo
bar
baz
```
</details>
</details>

## Q3

```php
<?php

$x = ['foo', 'bar'];
$y = ['baz', 'qux'];
       // ↓      ↓
var_dump([...$x, ...$y]);
```

<details>
<summary>回答</summary>

:::note success
**配列内での数値キーのアンパック** です。 PHP 7.4 以降で使用可能です。

- [配列内での値のアンパック ¶](https://www.php.net/manual/ja/migration74.new-features.php#migration74.new-features.core.unpack-inside-array
)
- **<font color=blue>配列を含む任意の `iterable` に対して適用できます。</font>**

以下の記述は等価です。

```php
[...$x, ...$y];
array_merge(
    is_array($x) ? $x : iterator_to_array($x),
    is_array($y) ? $y : iterator_to_array($y),
);
```
:::

<details>
<summary>出力</summary>

```
array(4) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  string(3) "baz"
  [3]=>
  string(3) "qux"
}
```
</details>
</details>

## Q4

```php
<?php

enum Gender
{
    case MALE;
    case FEMALE;
    case OTHER;
}

class Person
{
    public static function fromArray(array $props): self
    {
                     // ↓ ①
        return new self(...$props);
    }

    public function __construct(
        public readonly string $name,
        public readonly int $age,
        public readonly Gender $gender,
        public readonly string $country,
    ) {
    }
}

$common = [
    'age' => 20,
    'country' => 'United States',
];
$bob = [
    // ↓ ②
    ...$common,
    'name' => 'Bob',
    'gender' => Gender::MALE,
];
$alice = [
    // ↓ ③
    ...$common,
    'name' => 'Alice',
    'gender' => Gender::FEMALE,
    'country' => 'United Kingdom',
];

var_dump(
    Person::fromArray($bob),
    Person::fromArray($alice),
    new Person(
        // ↓ ④
        ...$common,
        name: 'Bob',
        gender: Gender::MALE,
    ),
    new Person(
        // ↓ ⑤
        ...$common,
        name: 'Alice',
        gender: Gender::FEMALE,
        country: 'United Kingdom',
    ),
);
```

<details>
<summary>回答</summary>

:::note success
- ① **名前付き引数のアンパック** です。名前付き引数が導入された PHP 8.0 以降で使用可能です。引数のアンパック自体は PHP 5.6 で対応されていましたが，名前付き引数にも対応するように拡張されました。
  - [名前付き引数 ¶](https://www.php.net/manual/ja/functions.arguments.php#functions.named-arguments) 
- ②③ **配列内での文字列キーのアンパック** です。 PHP **<font color=red>8.1</font>** 以降で使用可能です。PHP 7.4 時点では数値キーにしか対応しておらず， 名前付き引数が導入されたタイミングの PHP 8.0 でも対応されませんでした。 PHP 8.0 以前では，文字列キーが含まれる場合は `array_merge()` を依然として使わなければなりませんでした。
  - [文字列をキーとして持つ配列のアンパック ¶](https://www.php.net/manual/ja/migration81.new-features.php#migration81.new-features.core.unpacking-string-keys)
- ④ **名前付き引数に前置された引数のアンパック** です。 PHP **<font color=red>8.1</font>** 以降で使用可能です。 PHP 8.0 時点では，可変長引数と名前付き引数を同時に使うことはできませんでした。
  - [引数を展開した後の名前付き引数 ¶](https://www.php.net/manual/ja/migration81.new-features.php#migration81.new-features.core.named-arg-after-unpack)
  - 名前付き引数に引数のアンパックを<ins>後置</ins>することは， PHP 8.1 時点でも依然として<font color=red>不可能</font>です。
:::

:::note alert
⑤ 実行時エラーになります。配列では `array_merge()` 互換の上書きが許されていましたが，**引数では上書きが許されません**。
```
Named parameter $country overwrites previous argument
```
:::

<details>
<summary>出力 （エラーになるものを除外）</summary>

```
object(Person)#3 (4) {
  ["name"]=>
  string(3) "Bob"
  ["age"]=>
  int(20)
  ["gender"]=>
  enum(Gender::MALE)
  ["country"]=>
  string(13) "United States"
}
object(Person)#4 (4) {
  ["name"]=>
  string(5) "Alice"
  ["age"]=>
  int(20)
  ["gender"]=>
  enum(Gender::FEMALE)
  ["country"]=>
  string(14) "United Kingdom"
}
object(Person)#5 (4) {
  ["name"]=>
  string(3) "Bob"
  ["age"]=>
  int(20)
  ["gender"]=>
  enum(Gender::MALE)
  ["country"]=>
  string(13) "United States"
}
```
</details>
</details>

## Q5

```php
<?php

// あり得る値の定義
enum Permission: string
{
    case MEMBER_ADD = 'member:add';
    case MEMBER_DELETE = 'member:delete';
    case POST_CREATE = 'post:create';
}

// 信頼できない入力
$inputs = [
    'member:add',
    'post:create',
    'foo:bar', // 間違った値
];

// フィルタリングした結果
$filtered = array_values(array_filter(array_map(
                     // ↓
    Permission::tryFrom(...),
    $inputs,
)));

var_dump($filtered);
```

<details>
<summary>回答</summary>

:::note success
**[クロージャの生成](https://www.php.net/manual/ja/functions.first_class_callable_syntax.php#functions.first_class_callable_syntax)** です。 PHP 7.1 から [Closure::fromCallable()](https://www.php.net/manual/ja/closure.fromcallable.php) は使えましたが，静的解析と相性のいい方法として PHP **<font color=red>8.1</font>** から使えるようになりました。

- [第一級callableを生成する記法 ¶](https://www.php.net/manual/ja/functions.first_class_callable_syntax.php#functions.first_class_callable_syntax)

以下の記述はすべて等価です。

```php
Permission::tryFrom(...);
Closure::fromCallable([Permission::class, 'tryFrom']);
Closure::fromCallable(Permission::class . '::tryFrom');
```
:::

<details>
<summary>出力</summary>

```
array(2) {
  [0]=>
  enum(Permission::MEMBER_ADD)
  [1]=>
  enum(Permission::POST_CREATE)
}
```
</details>
</details>

## 関連記事

https://qiita.com/mpyw/items/7853e1864c3fcb338655