66
49

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-06-27

下記で使われている ... に関して

  • 🔰 シンタックスは,そもそも有効でしょうか?
  • 🔰 有効である場合,どういう役目を持っているでしょうか?
  • 🎓 有効である場合,どの PHP バージョン以降で有効でしょうか?

ヒント: 1つだけ,エラーになるものが含まれています!

Q1

<?php

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

var_dump($collapsed);
回答

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

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

$collapsed = array_merge([], ...$nested); // PHP 7.3 以前ではこう書く
出力 ネストを一段アンラップしたものがマージされます。
array(4) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  string(3) "baz"
  [3]=>
  string(3) "qux"
}

Q2

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

writeLine("foo");
writeLine("bar", "baz");
回答

可変長引数 です。 PHP 5.6 以降で使用可能です。

出力
foo
bar
baz

Q3

<?php

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

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

以下の記述は等価です。

[...$x, ...$y];
array_merge(
    is_array($x) ? $x : iterator_to_array($x),
    is_array($y) ? $y : iterator_to_array($y),
);
出力
array(4) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  string(3) "baz"
  [3]=>
  string(3) "qux"
}

Q4

<?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',
    ),
);
回答
  • 名前付き引数のアンパック です。名前付き引数が導入された PHP 8.0 以降で使用可能です。引数のアンパック自体は PHP 5.6 で対応されていましたが,名前付き引数にも対応するように拡張されました。
  • ②③ 配列内での文字列キーのアンパック です。 PHP 8.1 以降で使用可能です。PHP 7.4 時点では数値キーにしか対応しておらず, 名前付き引数が導入されたタイミングの PHP 8.0 でも対応されませんでした。 PHP 8.0 以前では,文字列キーが含まれる場合は array_merge() を依然として使わなければなりませんでした。
  • 名前付き引数に前置された引数のアンパック です。 PHP 8.1 以降で使用可能です。 PHP 8.0 時点では,可変長引数と名前付き引数を同時に使うことはできませんでした。

⑤ 実行時エラーになります。配列では array_merge() 互換の上書きが許されていましたが,引数では上書きが許されません

Named parameter $country overwrites previous argument
出力 (エラーになるものを除外)
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"
}

Q5

<?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);
回答

クロージャの生成 です。 PHP 7.1 から Closure::fromCallable() は使えましたが,静的解析と相性のいい方法として PHP 8.1 から使えるようになりました。

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

Permission::tryFrom(...);
Closure::fromCallable([Permission::class, 'tryFrom']);
Closure::fromCallable(Permission::class . '::tryFrom');
出力
array(2) {
  [0]=>
  enum(Permission::MEMBER_ADD)
  [1]=>
  enum(Permission::POST_CREATE)
}

関連記事

66
49
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66
49

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?