PHPのGenerator::getReturn()でジェネレータの最終結果を受け取る
Generator::getReturn()は、ジェネレータ関数がreturn文で返した値を、イテレーション完了後に取り出すためのメソッドです。yieldされた値とは別枠で扱われる点がポイントです。
ジェネレータを使っていると、途中の値はyieldで淡々と取り出せるのに、最後にreturnした値だけどこにも出てこない、という場面に出くわしたことがあるかもしれません。foreachで律儀に回しても、returnの値だけは無視されます。これは仕様で、そのための専用の受け取り口がgetReturn()です。
returnの値はforeachには出てこない
まず挙動を確認します。次のジェネレータは1、2をyieldしたあと、3をreturnしています。
$gen = (function () {
yield 1;
yield 2;
return 3;
})();
foreach ($gen as $val) {
echo $val, PHP_EOL;
}
echo $gen->getReturn(), PHP_EOL;
// 出力: 1, 2, 3(最後の3だけgetReturn経由)
foreachのループ本体には1と2しか流れてきません。returnの3はループが終わったあとに、$gen->getReturn()を呼んで初めて手に入ります。yieldは「途中経過を1つずつ渡す」ためのもの、returnは「処理が終わったときの最終結果を渡す」ためのもので、役割がそもそも違うと考えると腑に落ちます。
なぜyieldと同じ扱いにしないのか
一括処理のジェネレータで、1件ずつの値と「全体の集計結果」を両方返したい場面はよくあります。例えばCSVを1行ずつyieldしつつ、最後に処理件数の合計を返したいようなケースです。yieldの列に集計値を混ぜてしまうと、呼び出し側は「これはデータ行なのか集計なのか」を毎回判定しなければなりません。returnとgetReturn()を分けておけば、ループ中は素直にデータ行だけを扱い、ループが終わったあとにgetReturn()で集計を受け取れます。個人的には、この分離のおかげでforeach本体のコードがシンプルに保てるのがありがたいところだと思っています。
getReturn()はいつ呼んでいいのか
getReturn()は、ジェネレータの実行が完了したあとでないと正しい値を返しません。途中の状態で呼ぶとエラーになります。「returnを書いたかどうか」ではなく「最後まで進んだかどうか」で判定される点に注意してください。
$gen = (function () {
yield 1;
return 2;
})();
// まだ1回もイテレーションしていない
// echo $gen->getReturn(); // ここで呼ぶとエラーになる
foreach ($gen as $val) {
// 最後まで回し切る
}
echo $gen->getReturn(); // ここでは 2 が取れる
実務でハマりやすいのは、breakで途中離脱したケースです。ループをbreakで抜けた場合、ジェネレータはまだ「実行完了」していないので、その直後にgetReturn()を呼んでも欲しい値は取れません。全件処理を前提にした集計値を最後に受け取りたいなら、途中でbreakしない設計にするか、breakする可能性がある処理ではgetReturn()に依存しない設計にする必要があります。
まとめ
Generator::getReturn()は、ジェネレータのreturn文の値をイテレーション後に取り出すための専用メソッドです。yieldされる値の列と、最終結果としてのreturnの値は別の経路で運ばれるという前提を押さえておくと、1行ずつの処理と全体の集計を両立させるジェネレータが書きやすくなります。ただし呼べるのはジェネレータが最後まで実行を終えたあとだけなので、break等で途中離脱する可能性があるループでは扱いに注意が必要です。
よくある質問
Q. getReturn()はPHP何から使えますか?
A. PHP 7.0でジェネレータのreturn式(Generator Return Expressions)が導入されて以降、PHP 7系・8系のいずれでも使えます。
Q. return文を書かなかった場合、getReturn()はどうなりますか?
A. return文がなければ戻り値はnullとして扱われ、getReturn()もnullを返します。ジェネレータが最後まで実行を終えていることが前提です。
Q. yield fromで別のジェネレータに処理を委譲した場合のreturn値はどうなりますか?
A. yield fromで委譲した先のジェネレータがreturnした値は、その式自体の評価値になります。呼び出し元のジェネレータでその値を保持して自分のreturnに使う、という書き方が一般的です。