PHP における Generator の使い所

PHP5.5 から導入された Generator の使い方や、そのメリットなどを紹介します。

PHP5.5 から Generator が導入され、 foreach などで利用可能な Iterator オブジェクトが簡単に実装出来るようになりました。

何か、繰り返しの処理を実行する際には、通常配列を用いるケースがほとんどですが、Generator を用いることで 省メモリで大規模なデータも取り扱う事ができるようになります。

Generator

generator は 通常の関数の記法のなかで yield を使用するだけで簡単に作成することができます。

<?php 
function count10($start) {
    $i = 0;
    while($i < 10){
        yield $start + $i;
        $i++;
    }
}

foreach(count10(5) as $number){
    echo $number . PHP_EOL; 
}

上記の例では count10 というGeneratorを定義して 5 から始まる10個の数字を画面に出力します。

var_dump(count10(5));
// class Generator#2 (0) {
// }

yield を含む Generator 関数は、実行されると Generator オブジェクトを生成します。 foreach で繰り返しループ処理を実施することで、中の処理が実行され、 yield で指定した値が逐次吐き出されます。

foreach を利用する他にも current next を利用して値を取得することも可能です。

$g = count10(5);
$number = $g->current(); // 5
$g->next();
$number = $g->current(); // 6
$number = $g->current(); // 6
$g->next();
...
$number = $g->current(); // 14
$g->next();
$number = $g->current(); // null
$g->next(); // エラーにならない

current はyield で返却される値を取得します。 next はジェネレータの処理を一つ先にすすめます。

なお、Generator は前進専用の Iterator オブジェクトとして定義されているため、通常の Iterator オブジェクトのような rewind を利用した ポインタの巻き戻し処理を行うことはできません。

値とキーによる yield

yeild $key => $value の構文を用いることで、yield 時にキー情報を添える事が出来ます。

<?php 
function count10($start) {
    $i = 0;
    while($i < 10){
        yield $i+1 => $start + $i;
        $i++;
    }
}

foreach(count10(5) as $key => $number){
    echo "{$key} => {$number}" . PHP_EOL; 
}

// result
// 1 => 5
// 2 => 6
// 3 => 7
// ...
// 10 => 14

手動で Generator を実行する際には、current()で値を取得するのと同じように key() でキーを取得します。

yield from による Generator の合成

また既存の Generator を利用して別の Generator を作成する事もできます。
また

<?php 
function count10($start) {
    $i = 0;
    while($i < 10){
        yield $i+1 => $start + $i;
        $i++;
    }
}

function count20($start){
    yield from [1,2,3,4,5];
    yield from count10($start);    
}

yield from に 配列やGenerator などの Iterator オブジェクトを続けることで、簡単に複数のIterator を合成する事ができます。

Generator 内部へ値を渡す

Generator の send メソドを利用して yield に戻り値を与える事が可能です。

function createUser() {
    while (true) {
        $string = yield;
        echo $string.PHP_EOL;
    }
}

$printer = printer();
$printer->send('Hello world!');
$printer->send('Bye world!');

send の引数に与えた値が yield の戻り値 ($string = yield$string) として取得できます。

Generator のメリット

配列を使用するケースとの比較

配列を使用するケースと比べ、Generator を使用した記述はきわめて省メモリで動作します。

巨大なCSVファイルの取り扱いや、大規模なDB結果の取り扱いなど、メモリに乗り切らない処理を記述する際には非常に役にたつでしょう。

fgets を利用した1行づつのファイル読み込みや LIMIT/OFFSET を利用したSQL処理をそのまま操作するのではなくGeneratorとしてラップすることで処理をシンプルに実行することができます。

いちど作成したジェネレータは、それぞれが個別の実行スコープを持つため、処理の再利用性も大きく向上します。

Iterator を利用するケースとの比較

Generator と同様の機能は Iterator インターフェイスを実装したオブジェクトを作成することでも実現できます。

http://php.net/manual/ja/class.iterator.php

しかし、 Iterator の実装には複数のメソドの記述が必要でやや処理も複雑になります。 rewind が利用できない一方通行の Iterator で問題無いケースでは Generator を利用する方が良いでしょう。

カテゴリー: PHP

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です