...が意外に便利だった件

はじめまして。
今月からRoxxな人になった前田といいます。 よろしくおねがいします。

PHPでは...というキーワードがあります。

可変長引数と引数のアンパックです。

以下、例です。

<?php
// 可変長引数
function printArgs(...$inputs)
{
    echo $inputs[0]; // apple
    echo $inputs[1]; // banana
    echo $inputs[2]; // chocolate
}

printArgs('apple', 'banana', 'chocolate');
<?php
// 引数のアンパック
function printArgs($a, $b, $c)
{
// 略
}

$args = ['aaple', 'banana', 'chocolate'];
printArgs(...$args);

前から存在は知っていたのですが、わりと自分の中ではただのトリビアになっていて、あまり使用してきませんでした。
というより使用する場面をいまいち把握できていませんでした。

最近、個人で開発しているときに「あれ、意外と...は便利なのでは!?」となったので紹介します。

事例

以下の仕様のクラスを考えます。

  • ユーザ定義クラスStatusがある
  • Statusを複数まとめて扱うクラスがほしい
    • つまり、配列をラップしたクラス
  • 以下の要件を備える

...をつかわない場合

以下のようになると思います。

<?php
class StatusCollection
{
    /** @var Status[] */
    private $items;
    
    public function __construct(array $items)
    {
        foreach ($items as $item) {
            if (!$item instanceof Status) {
                throw new InvalidArgumentException();
            }
        }
        
        $this->items = array_values($items);
    }
}

$items = [Status::on(), Statuss::off(), Status::unknown()];
$statuses = new StatusCollection($items);

ポイントとしては、Status以外の要素がある場合は例外を投げていること、 array_values でキーを振り直しているところです。

こういったバリデーションに近い部分にコードを多く使うと読みづらいですね。

こういった読みづらさを...を使うと解消できます。

array_valuesを取り除く

まず array_values を消していきます。

<?php
class StatusCollection
{
    /** @var Status[] */
    private $items;
    
    public function __construct(...$items)
    {
        foreach ($items as $item) {
            if (!$item instanceof Status) {
                throw new InvalidArgumentException();
            }
        }
        
        $this->items = $items;
    }
}

$items = [Status::on(), Statuss::off(), Status::unknown()];
$statuses = new StatusCollection(...$items);

コンストラクタの仮引数を ... に変更しました。
これにより、キーの採番はPHPが勝手に行うようになるので、array_valuesが不要になります。
また、仮引数を可変長引数にしたので、実引数も忘れずに...でアンパックします。

型チェックのコードを取り除く

次に一番面積をとっているforeachを消していきます。

<?php
class StatusCollection
{
    /** @var Status[] */
    private $items;
    
    public function __construct(Status ...$items)
    {
        $this->items = $items;
    }
}

$items = [Status::on(), Statuss::off(), Status::unknown()];
$statuses = new StatusCollection(...$items);

単純に型宣言(タイプヒント)を使用しています。
...を使う前は配列を受け取っていたのでarrayで指定していましたが、...を使うことで配列の要素の型を指定できます。

面積をとっていたforeachを削除できたので、とても見通しの良いコードになりました。

まとめ

  • ...を使用することで、配列のキーを気にしなくて済む
  • ...を使用することで、配列の要素の型を指定できる

...は型宣言とセットで使用することでパワーを発揮しそうです。 「連想配列じゃなくてもいいんだけど・・・」という場合も結構あるので、配列のキーを気にしなくて良い場合は積極的に使っていこうと思います。