ジェネレータとは何か
PHPには**ジェネレータ(Generator)**という、メモリ効率の良い反復処理を実現する仕組みがあります。PHP 5.5から導入されたこの機能は、大量のデータを一度にメモリへ展開せず、必要なときに1件ずつ値を生成できるのが特徴です。
通常の配列ですべての値を返す関数と比較してみましょう。
<?php
// 通常の関数:すべての値を配列に格納してから返す
function rangeArray(int $start, int $end): array
{
$result = [];
for ($i = $start; $i <= $end; $i++) {
$result[] = $i;
}
return $result; // 100万件なら100万件分のメモリを消費
}
// ジェネレータ:1件ずつ値を生成する
function rangeGenerator(int $start, int $end): Generator
{
for ($i = $start; $i <= $end; $i++) {
yield $i; // ここで一時停止し、呼び出し元に値を返す
}
}
foreach (rangeGenerator(1, 1_000_000) as $value) {
// 常に1件分のメモリしか使わない
}
yieldキーワードが登場したとき、関数は値を返して一時停止します。次に呼び出されると、停止した箇所から処理が再開されます。この仕組みによって、大量データでもメモリ消費を最小限に抑えられます。
yieldの基本的な使い方
キーと値のペアを返す
yieldは配列と同様に、キーと値のペアで値を生成できます。
<?php
function indexedColors(): Generator
{
yield 'red' => '#FF0000';
yield 'green' => '#00FF00';
yield 'blue' => '#0000FF';
}
foreach (indexedColors() as $name => $hex) {
echo "{$name}: {$hex}\n";
}
// red: #FF0000
// green: #00FF00
// blue: #0000FF
yieldの戻り値を受け取る
send()メソッドを使うと、外部からジェネレータに値を渡すことができます。yield式の評価値として受け取れます。
<?php
function logger(): Generator
{
while (true) {
$message = yield; // 外から値を受け取る
if ($message === null) {
return;
}
echo '[LOG] ' . $message . "\n";
}
}
$log = logger();
$log->current(); // ジェネレータを最初のyieldまで進める
$log->send('アプリケーション起動');
$log->send('ユーザーがログイン');
$log->send('処理完了');
// [LOG] アプリケーション起動
// [LOG] ユーザーがログイン
// [LOG] 処理完了
実践的な使用例
CSVファイルの行単位読み込み
大きなCSVファイルを処理するときにジェネレータは非常に効果的です。
<?php
function readCsvLines(string $filepath): Generator
{
$handle = fopen($filepath, 'r');
if ($handle === false) {
throw new RuntimeException("ファイルを開けません: {$filepath}");
}
try {
while (($line = fgetcsv($handle)) !== false) {
yield $line;
}
} finally {
fclose($handle); // 必ずファイルを閉じる
}
}
// 使い方
foreach (readCsvLines('large_data.csv') as $row) {
// 1行ずつ処理。ファイル全体をメモリに読み込まない
[$id, $name, $email] = $row;
echo "ID: {$id}, 名前: {$name}\n";
}
ページネーション付きAPIの取得
外部APIを複数ページにわたって取得する処理もジェネレータでスッキリ書けます。
<?php
function fetchAllUsers(string $baseUrl): Generator
{
$page = 1;
while (true) {
$response = file_get_contents("{$baseUrl}?page={$page}");
$data = json_decode($response, true);
if (empty($data['users'])) {
return; // データがなければ終了
}
foreach ($data['users'] as $user) {
yield $user; // 1ユーザーずつ返す
}
if (!isset($data['next_page'])) {
return; // 次のページがなければ終了
}
$page++;
}
}
foreach (fetchAllUsers('https://api.example.com/users') as $user) {
echo $user['name'] . "\n";
}
yield fromで別のジェネレータに委譲する
PHP 7から導入されたyield fromを使うと、別のジェネレータやイテラブルに処理を委譲できます。
<?php
function innerGenerator(): Generator
{
yield 1;
yield 2;
yield 3;
}
function outerGenerator(): Generator
{
yield 0;
yield from innerGenerator(); // 内部のジェネレータに委譲
yield 4;
}
foreach (outerGenerator() as $value) {
echo $value . "\n";
}
// 0, 1, 2, 3, 4
yield fromは配列にも使えます。
<?php
function combined(): Generator
{
yield from [10, 20, 30];
yield from new ArrayIterator([40, 50]);
yield 60;
}
通常の配列との使い分け
| 観点 | 配列 | ジェネレータ |
|---|---|---|
| メモリ消費 | 全件分 | 常に1件分 |
| 再利用性 | 何度でも | 一方向のみ |
| ランダムアクセス | 可能 | 不可 |
| 向いているケース | 小〜中規模データ | 大量データ・無限列 |
ジェネレータはすでに処理した値に戻れません。もし複数回ループしたい場合は、iterator_to_array()で配列に変換するか、通常の配列を使いましょう。
<?php
$gen = rangeGenerator(1, 5);
$array = iterator_to_array($gen); // [1, 2, 3, 4, 5]
まとめ
PHPのジェネレータは、大量データの処理やストリーム処理においてメモリ効率を大幅に改善できる強力な機能です。
yieldで値を1件ずつ返して処理を一時停止できる- キーと値のペアや
send()による双方向通信も可能 yield fromで別のジェネレータや配列に委譲できる- CSVや外部API取得など、大量・逐次データの処理に最適
最初は難しく感じるかもしれませんが、まずは大きなファイルや大量レコードの処理でジェネレータに置き換えてみると、その恩恵を実感できるはずです。