この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:T
更新日時:2026-06-11 07:07:02

タイトル: returnとyieldの違い
SEOタイトル: PHP yield (ジェネレータ) と return の違い完全ガイド

この記事の要点
  • return は関数を終了して 1 つの値を返す。呼び出し元に制御を戻して終わり
  • yield は関数を一時停止して値を生成。次回呼び出しで続きから再開する ジェネレータ
  • yield を含む関数は自動的に Generator オブジェクトを返す。配列ではなく反復可能オブジェクト
  • 巨大配列を作らずに済むのでメモリ効率が劇的に良い (100 万件でも数 KB)
  • 使い分け: 一度に全部必要なら return、順次処理で済むなら yield。Laravel の lazy() / lazyById() も内部はジェネレータ

return と yield の基本動作の違い

// return: 1 回呼んで終わり
function range_return(int $n): array {
    $result = [];
    for ($i = 0; $i < $n; $i++) {
        $result[] = $i;
    }
    return $result;     // ★ ここで終了、全要素を含む配列を返す
}

// yield: 1 回ごとに値を吐き出す
function range_yield(int $n) {
    for ($i = 0; $i < $n; $i++) {
        yield $i;       // ★ 一時停止して値を返す、次回ここから再開
    }
}

// 利用は同じ foreach で書ける
foreach (range_return(5) as $v) { echo $v; }  // 01234
foreach (range_yield(5)  as $v) { echo $v; }  // 01234

メモリ効率の決定的な差

// 100 万件で比較
function arr_return(): array {
    $a = [];
    for ($i = 0; $i < 1_000_000; $i++) {
        $a[] = "item_$i";
    }
    return $a;
}

function arr_yield() {
    for ($i = 0; $i < 1_000_000; $i++) {
        yield "item_$i";
    }
}

// メモリ計測
echo memory_get_usage() . PHP_EOL;     // ベース: 約 400 KB

$a = arr_return();
echo memory_get_usage() . PHP_EOL;     // 約 60 MB ❌

unset($a);

foreach (arr_yield() as $v) {
    // 1 件ずつ処理
}
echo memory_get_usage() . PHP_EOL;     // 約 400 KB ✅

yield の構文バリエーション

function basics() {
    yield 1;                          // 値のみ (キーは 0 から自動)
    yield 2;
    yield 3;
}

function withKey() {
    yield 'name' => '太郎';            // ★ キー => 値
    yield 'age'  => 30;
}

function fromAnother() {
    yield 1;
    yield from [2, 3, 4];             // ★ 他の iterable を流し込む
    yield from basics();              // ジェネレータ from ジェネレータ
    yield 5;
}

// 受け取り方
foreach (withKey() as $k => $v) {
    echo "$k = $v\n";
}
// name = 太郎
// age  = 30

Generator クラスのメソッド

yield を含む関数は Generator オブジェクトを返します。foreach 以外にも直接メソッドを呼べます:

$gen = range_yield(5);

$gen->current();   // 現在の値
$gen->key();       // 現在のキー
$gen->next();      // 次へ進める
$gen->valid();     // まだ要素があるか
$gen->rewind();    // 巻き戻し (※ 一度進めた後は不可)
$gen->send($v);    // ジェネレータに値を送る (双方向通信)
$gen->getReturn(); // ジェネレータ終了後の return 値を取得

// 手動でループ
while ($gen->valid()) {
    echo $gen->current() . "\n";
    $gen->next();
}

ジェネレータ内の return

ジェネレータ関数内でも return が使えます。値はジェネレータ終了後に getReturn() で取得可能:

function counter() {
    $i = 0;
    while ($i < 3) {
        yield $i++;
    }
    return 'finished';  // ★ 終了時の戻り値
}

$gen = counter();
foreach ($gen as $v) {
    echo "$v\n";       // 0, 1, 2
}
echo $gen->getReturn(); // finished

典型ユースケース1: 巨大ファイル処理

// ❌ 全部メモリに読む → OOM 確定
$lines = file('huge.csv');
foreach ($lines as $line) { ... }

// ✅ 行単位でストリーミング
function readLines(string $path) {
    $fp = fopen($path, 'r');
    while (($line = fgets($fp)) !== false) {
        yield trim($line);
    }
    fclose($fp);
}

foreach (readLines('huge.csv') as $line) {
    // 1 行ずつ処理 → メモリ一定
}

典型ユースケース2: 無限ストリーム

// 無限フィボナッチ
function fibonacci() {
    [$a, $b] = [0, 1];
    while (true) {
        yield $a;
        [$a, $b] = [$b, $a + $b];
    }
}

$count = 0;
foreach (fibonacci() as $n) {
    echo "$n ";
    if (++$count >= 10) break;
}
// 0 1 1 2 3 5 8 13 21 34

Laravel での lazy / lazyCollection

Laravel 6+ では Eloquent / Collection にジェネレータベースの API が用意されています:

// ❌ get() は全件メモリ展開 → 大量データで OOM
foreach (User::all() as $user) { ... }

// ✅ lazy() は内部 yield で 1000 件ずつ取得
foreach (User::lazy() as $user) {
    // メモリ一定で全件処理
}

// ID 順で安全に lazy 取得 (推奨)
foreach (User::lazyById() as $user) { ... }

// Collection の lazy
LazyCollection::make(function () {
    $fp = fopen('big.csv', 'r');
    while (($line = fgets($fp)) !== false) {
        yield $line;
    }
})->each(fn($l) => process($l));

return vs yield 使い分け

状況推奨
結果が小さい (数件〜数百件)return + 配列
結果が大きい / メモリ気になるyield
呼び出し側で count() や添字アクセスが必要return + 配列
呼び出し側は foreach で十分yield
無限列 / 終わりのないストリームyield 一択
JSON にエンコードして返す APIreturn (Generator は json_encode 不可)

テスト方法

use PHPUnit\Framework\TestCase;

class GeneratorTest extends TestCase {
    public function test_range_yield(): void {
        $result = iterator_to_array(range_yield(3));
        $this->assertSame([0, 1, 2], $result);
    }

    public function test_yields_3_items(): void {
        $gen = range_yield(3);
        $this->assertCount(3, iterator_to_array($gen));
    }
}

FAQ

Q: ジェネレータは何度も foreach できる?
A: できません。1 度走り切ると終了。再度回すには関数を呼び直す必要があります ($gen = range_yield(5) を再実行)。

Q: ジェネレータを array にしたい
A: iterator_to_array($gen)。ただし全件メモリ展開されるので yield の利点が消えます。

Q: PHP のどのバージョンから使える?
A: PHP 5.5+ で yield、PHP 7.0+ で yield from、PHP 7.0+ でジェネレータの return 値サポート。