7
8

More than 5 years have passed since last update.

Generator は Countable を実装しないので count 関数にそのまま渡すことを避ける

Last updated at Posted at 2016-10-04

概要

Generator は Countable を実装しないので、count 関数に渡すと常に1の値が返されます。1 が返されることはマニュアルに明記されています。要素の数がゼロであるかの判定をする際に Generator を配列と同じように考えると正しく処理できない可能性があります。PHP Internals に提出された RFC は典型的な間違えとして次のコードを示しています。

function handle_records(iterable $iterable)
{
    if (count($iterable) === 0) {
        return handle_empty();
    }

    foreach ($iterable as $value) {
        handle_value($val);
    }
}

iterablearray および Traversable を実装するオブジェクトの両方を引数として受け入れます。PHP 7.1 で導入されました (RFC)。同時に is_iterable 関数も導入されました。

このコードでは $iterable が Generator の場合、count($iterable) === 0 の行が常に false になるので、handle_empty() は常に実行されません。

この RFC は Countable を実装しないオブジェクトに対して PHP は警告を発するべきではないかと提案しています。

コードの例

かんたんな Generator を定義して、count の戻り値が 1 になるか確認してみましょう。

function xrange($start, $limit, $step = 1) {
    for ($i = $start; $i <= $limit; $i += $step) {
        yield $i;
    }
}

var_dump(
    1 === count(xrange(1, 5))
);

対策

count 関数を適用する前に配列もしくは Countable を実装するオブジェクトであることをチェックします。複数の箇所で count を使うのであれば、次のような関数を定義するとよいでしょう。

function is_countable($array_or_object) {
    return is_array($array_or_object) || $array_or_object instanceof Countable;
}

var_dump(
    true === is_countable(range(1, 5)),
    false === is_countable(xrange(1, 5))
);

一番最初に示したコードは次のように修正されます。

if (is_countable($iterable) && count($iterable) === 0) {
    return handle_empty();
}

count の引数の改善も考えましょう。最初に Generator を iterator_to_array で配列に変換してから count に渡す方法を挙げます。

var_dump(
    5 === count(iterator_to_array(xrange(1, 5), false))
);

次に IteratorAggregateCountable を実装するクラスを用意する方法を挙げます。クラスを再利用しないのであれば、PHP 7.0 で導入された匿名クラスを利用します。

$gen = new class implements IteratorAggregate, Countable {
    public function getIterator() {
        return xrange(1, 5);
    }
    public function count() {
        $count = 0;
        foreach ($this as $value) { $count++; }
        return $count;
    }
};

var_dump(
    5 === count($gen)
);
7
8
0

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
7
8