PHP yieldの使い方 | ジェネレータで大量データを省メモリ処理する方法

by

カテゴリー:

「PHP yield」は、PHPプログラミングにおいて非常にパワフルな機能であり、効率的なデータ処理やメモリの最適化に大いに貢献します。

この記事では、PHPのyieldステートメントを使用したジェネレータの使い方を学び、実際のコード例を提供します。

「php yield」を使いこなすことは、データベースクエリの結果セットを逐次的に処理したり、大規模なデータセットを扱う際のメモリ管理を改善するのに役立ちます。

あなたのプロジェクトでもジェネレーターを使いこなすことでコードの効率性とパフォーマンスを向上させることが期待できるようになることでしょう。

それでは、PHP yieldについて解説していきます。

結論:yieldの最短の使い方

まず結論から。関数の中に yield を書くと、その関数は値を1つずつ返す「ジェネレータ」になります。return で配列を一気に返す代わりに、yield で値を都度返すイメージです。

<?php

function gen() {
    yield 1;
    yield 2;
    yield 3;
}

foreach (gen() as $value) {
    echo $value; // 123
}

ポイントは、yield を含む関数は呼び出しても本体がすぐには実行されず、foreach などで値を取り出すたびに、次の yield まで処理が進むという点です。全データをメモリに保持しないため、大きなデータでもメモリ消費を抑えられます。

yieldの書き方パターン一覧

yield にはいくつかの書き方があります。用途に応じて使い分けましょう。

書き方意味
yield $value;値だけを順に返す(キーは0から自動採番)
yield $key => $value;キーと値のペアを返す(連想配列のように扱える)
yield;null を返す(値を渡さずに一時停止だけしたい場合)
yield from $iterable;別の配列やジェネレータの値をまとめて委譲する
$x = yield $value;send() で外部から値を受け取る

yield $key => $valueyield from の例は次の通りです。

<?php

// キー付きで返す
function keyValueGen() {
    yield 'a' => 1;
    yield 'b' => 2;
}
foreach (keyValueGen() as $key => $value) {
    echo "$key => $value\n"; // a => 1 / b => 2
}

// yield from で別のジェネレータを委譲
function inner() {
    yield 1;
    yield 2;
}
function outer() {
    yield 0;
    yield from inner(); // 1, 2 を差し込む
    yield 3;
}
foreach (outer() as $value) {
    echo $value; // 0123
}

PHP yieldのサンプルコード

PHP yieldで実現できるジェネレータとは、通常の関数と同様に定義しますが、yieldステートメントを含めることが必要です。

yieldは、ジェネレータから次の値を生成し、その値を呼び出し元に返します。以下は、シンプルなジェネレータ関数の例です。

<?php

function simpleGenerator($count = 3) {
    for ($i=0;$i<$count;$i++) {
        yield $i;
    }
}

foreach (simpleGenerator() as $value) {
    echo $value . "\n";
}
/**
0
1
2
 */


foreach (simpleGenerator(5) as $value) {
    echo $value . "\n";
}
/**
0
1
2
3
4
 */


$generator = simpleGenerator();
var_dump($generator);
/**
object(Generator)#1 (0) {
}
 */


print_r(iterator_to_array($generator));
/**
Array
(
    [0] => 0
    [1] => 1
    [2] => 2
)
 */

このように、ジェネレータ関数を使うことができます。

サンプルではジェネレータ関数はデフォルトでは3回ループするようになっていますが、引数によってループ回数が可変するように実装ができます。

foreachで実行する以外にも iterator_to_array関数を使用することで配列に変換することができます。

ジェネレータの利点

ジェネレータは、大きなデータセットやファイルの内容など、一度に全てのデータをメモリに読み込むことが難しい場合に非常に有用です。

yieldを使用することで、データを逐次処理することができ、メモリの効率を向上させることができます。また、yieldを使うことで、処理を一時停止し、必要なときに再開することもできます。

このジェネレータが活用できるパターンとしてはデータベースから結果セットを取得し、yieldを使って1行ずつデータを生成する事が想定できます。

これにより、大規模なデータセットをメモリに一度に読み込まずに処理できます。

以下は、PDO(PHP Data Objects)を使用したデータ取得のサンプルコードです。

<?php

function fetchUsersWithYield(PDO $pdo) {
    $stmt = $pdo->prepare("SELECT * FROM users");
    $stmt->execute();

    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        yield $row;
    }
}

// PDO接続をセットアップ
try {
    $pdo = new PDO("mysql:host=localhost;dbname=your_database", "username", "password");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // ジェネレータを使用してデータを取得
    foreach (fetchUsersWithYield($pdo) as $user) {
        echo "ID: " . $user['id'] . ", Name: " . $user['name'] . "\n";
    }
} catch (PDOException $e) {
    echo "Error: " . $e->getMessage();
}

/**
ID: 1, Name: John
ID: 2, Name: Jane
ID: 3, Name: Alice
ID: 4, Name: Bob
*/

この方法を使用すると、データベースから大規模なデータセットを逐次処理できます。なお、limitやoffsetを使用することで更なる効率化も期待する事ができます。

データセットがメモリに収まり切らない場合や、効率的なデータ処理が必要な場合に特に役立ちます。ただし、データベースの種類や接続情報を適切に設定してください。

これは Laravelフレームワークの chunk を使用する方法と同様の事が可能となっております。

yield(ジェネレータ)とreturn(配列)の違い

yield と、配列を return する従来のやり方は、次のように使い分けます。

観点yield(ジェネレータ)return(配列)
メモリ消費1件ずつ生成するため少ない全件を保持するため多い
値の生成タイミング取り出すたびに遅延評価関数を抜けた時点で全件確定
再ループ基本は1回のみ(使い切ると再取得が必要)何度でもループ可能
件数の取得count() は不可(iterator_count() で消費される)count() で取得可能
向いている場面大量データ・逐次処理・無限列件数が少なく何度も使い回す場合

ざっくり言えば、「大きなデータを流し込みながら処理したいとき」は yield、「小さなデータを配列としてまとめて扱いたいとき」は return と覚えておくとよいでしょう。

yieldを使う際の注意点

  • ジェネレータは基本的に一度しか回せません。もう一度使いたい場合は関数を呼び直して新しいジェネレータを生成します。
  • 件数を知りたいときに iterator_count() を使うと値が消費され、その後 foreach しても何も取れなくなります。
  • PHP 7.0 以降ではジェネレータ内で return $value; を書くと、$generator->getReturn() で完了後の戻り値を取得できます。
  • 配列として扱いたい場合は iterator_to_array() で変換できますが、その時点で全件がメモリに載るためメモリ削減の効果は失われます。

最後に

以上、PHPでyieldを使用する方法をまとめていきました。

正直なところフレームワークを使用したデータベース取得や、PHPの標準関数を使用したファイル読み込みを使いこなしていれば問題に当たりづらいとは感じています。

ですが、ジェネレーターを使用することで解決できることはパフォーマンス改善にとても重要なことなので、マスターしておくことでご自分のプロジェクトに貢献できることかと思います。

今回もお疲れ様でした。

参考: https://www.php.net/manual/ja/language.generators.syntax.php

ホーム » 記事一覧 » 技術メモ » 開発 » PHP開発 » PHP yieldの使い方 | ジェネレータで大量データを省メモリ処理する方法

投稿者

ここまで読んでいただき、ありがとうございます。

このサイトでは、私自身が関わっていることや、興味を持って試していることをそのまま置いています。

内容を見ていて、
「少し話を聞いてみたい」
「協業できる部分がありそう」
と感じる点があれば、下記のお問い合わせフォームからご連絡ください。状況を確認したうえで、必要な範囲でお返事します。

特に、以下のWordPressサイトに関する相談にも対応しているのでご興味ありましたらご相談ください。


Comments

コメントを残す

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