生成器返回值
概述
从 PHP 7.0 开始,生成器函数支持使用 return 语句返回一个最终值。这个值可以通过 Generator::getReturn() 方法在生成器执行完毕后获取。生成器的返回值与 yield 产出的值是不同的概念:yield 产出的值是迭代过程中的数据,而 return 的值是生成器完成后的最终结果。
PHP 版本
生成器中的 return 语句和 Generator::getReturn() 方法从 PHP 7.0 开始可用。
基础概念
return 在生成器中的行为
在生成器函数中,return 语句会终止生成器的执行,并设置一个最终返回值。这个值不会被 foreach 遍历到,需要通过 Generator::getReturn() 显式获取。
Generator::getReturn() 方法
getReturn() 在生成器执行完毕(状态为 Finished)后调用,返回 return 语句的值。如果在生成器尚未完成时调用,会抛出异常。
最终值的用途
生成器的最终返回值通常用于:
- 返回聚合计算的结果(总和、平均值等)
- 返回处理状态(成功/失败/统计信息)
- 返回元数据或摘要
语法与代码
基本返回值
<?php
declare(strict_types=1);
function sumGenerator(array $numbers): \Generator
{
$sum = 0;
foreach ($numbers as $number) {
$sum += $number;
yield $number;
}
return $sum;
}
$gen = sumGenerator([1, 2, 3, 4, 5]);
// foreach 遍历产出的值
foreach ($gen as $value) {
echo "产出: {$value}\n";
}
// 产出: 1
// 产出: 2
// 产出: 3
// 产出: 4
// 产出: 5
// 获取最终返回值
echo "总和: " . $gen->getReturn() . "\n"; // 总和: 15getReturn 的调用时机
<?php
declare(strict_types=1);
function processGenerator(): \Generator
{
yield 'step 1';
yield 'step 2';
return 'completed';
}
$gen = processGenerator();
echo $gen->current() . "\n"; // step 1
// getReturn 在生成器未完成时调用会抛出异常
try {
$gen->getReturn();
} catch (\Exception $e) {
echo "异常: " . $e->getMessage() . "\n";
// Generator is still running (not finished)
}
$gen->next(); // step 2
$gen->next(); // 生成器结束
echo $gen->getReturn() . "\n"; // completed调用时机
Generator::getReturn() 只能在生成器完全执行完毕后调用。在生成器仍处于 Suspended 状态时调用会抛出 Exception。
返回复合值
<?php
declare(strict_types=1);
function analyzeGenerator(iterable $data): \Generator
{
$count = 0;
$sum = 0;
$min = PHP_INT_MAX;
$max = PHP_INT_MIN;
foreach ($data as $value) {
$count++;
$sum += $value;
$min = min($min, $value);
$max = max($max, $value);
yield $value;
}
return [
'count' => $count,
'sum' => $sum,
'average' => $count > 0 ? $sum / $count : 0,
'min' => $min === PHP_INT_MAX ? null : $min,
'max' => $max === PHP_INT_MIN ? null : $max,
];
}
$gen = analyzeGenerator([15, 23, 8, 42, 31, 19]);
foreach ($gen as $value) {
// 处理每个值
}
$stats = $gen->getReturn();
print_r($stats);
// Array ( [count] => 6 [sum] => 138 [average] => 23 [min] => 8 [max] => 42 )不使用 return 的生成器
<?php
declare(strict_types=1);
function noReturnGenerator(): \Generator
{
yield 1;
yield 2;
// 没有 return 语句
}
$gen = noReturnGenerator();
foreach ($gen as $v) {
// 遍历产出值
}
echo $gen->getReturn(); // 输出: (空)— 返回 null详细说明
return vs yield 的区别
| 特性 | yield | return |
|---|---|---|
| 执行效果 | 暂停生成器 | 终止生成器 |
| 获取方式 | current() / foreach | getReturn() |
| 多次使用 | 可以多次 yield | 只能 return 一次 |
| 返回类型 | 任意值 | 任意值 |
| foreach 可见 | 是 | 否 |
yield from 与 return 值
当使用 yield from 委托子生成器时,子生成器的 return 值会被 yield from 表达式捕获。
<?php
declare(strict_types=1);
function subTask(): \Generator
{
yield 'a';
yield 'b';
return 'sub result';
}
function mainTask(): \Generator
{
yield 'start';
// yield from 表达式的值就是子生成器的 return 值
$subResult = yield from subTask();
yield "sub returned: {$subResult}";
return 'main result';
}
$gen = mainTask();
foreach ($gen as $value) {
echo "{$value}\n";
}
// start
// a
// b
// sub returned: sub result
echo $gen->getReturn(); // main resultgetReturn 与迭代方式的关系
无论使用 foreach 还是手动调用 current()/next(),只要生成器完全执行完毕,getReturn() 都能正常工作。
<?php
declare(strict_types=1);
function dualAccess(): \Generator
{
yield 'first';
yield 'second';
return 'final';
}
// 方式一:foreach
$gen1 = dualAccess();
foreach ($gen1 as $v) { /* ... */ }
echo $gen1->getReturn(); // final
// 方式二:手动迭代
$gen2 = dualAccess();
while ($gen2->valid()) {
$gen2->next();
}
echo $gen2->getReturn(); // final实战示例
实战:数据处理管道的统计信息
<?php
declare(strict_types=1);
function dataProcessor(iterable $source): \Generator
{
$processed = 0;
$skipped = 0;
$errors = 0;
foreach ($source as $item) {
try {
$result = processItem($item);
if ($result !== null) {
yield $result;
$processed++;
} else {
$skipped++;
}
} catch (\Throwable $e) {
$errors++;
}
}
return [
'processed' => $processed,
'skipped' => $skipped,
'errors' => $errors,
];
}
function processItem(mixed $item): ?string
{
if (!is_numeric($item)) {
return null;
}
return 'item_' . ((int) $item * 2);
}
$source = [1, 'invalid', 3, null, 5, 'text', 7];
$gen = dataProcessor($source);
foreach ($gen as $result) {
echo "处理结果: {$result}\n";
}
$report = $gen->getReturn();
echo "处理: {$report['processed']}, 跳过: {$report['skipped']}, 错误: {$report['errors']}\n";
// 处理: 4, 跳过: 1, 错误: 2实战:文件处理的统计
<?php
declare(strict_types=1);
function countLinesAndWords(string $filePath): \Generator
{
$lineCount = 0;
$wordCount = 0;
$byteCount = 0;
$handle = fopen($filePath, 'r');
if ($handle === false) {
throw new \RuntimeException("无法打开文件");
}
try {
while (($line = fgets($handle)) !== false) {
$lineCount++;
$wordCount += str_word_count($line);
$byteCount += strlen($line);
yield $line;
}
} finally {
fclose($handle);
}
return [
'lines' => $lineCount,
'words' => $wordCount,
'bytes' => $byteCount,
];
}
// 使用生成器逐行处理文件,同时获取统计信息
$gen = countLinesAndWords('example.txt');
foreach ($gen as $line) {
if (str_contains($line, 'TODO')) {
echo "发现 TODO: " . trim($line) . "\n";
}
}
$stats = $gen->getReturn();
echo "行数: {$stats['lines']}, 词数: {$stats['words']}, 字节数: {$stats['bytes']}\n";实战:聚合生成器
<?php
declare(strict_types=1);
function aggregateFromMultiple(iterable ...$sources): \Generator
{
$totalProcessed = 0;
foreach ($sources as $source) {
foreach ($source as $item) {
yield $item;
$totalProcessed++;
}
}
return $totalProcessed;
}
$data1 = [1, 2, 3];
$data2 = ['a', 'b'];
$data3 = [true, false];
$gen = aggregateFromMultiple($data1, $data2, $data3);
foreach ($gen as $item) {
// 处理每一项
}
echo "总计处理: " . $gen->getReturn() . " 项\n";
// 总计处理: 7 项注意事项
PHP 7.0 之前不支持 return
在 PHP 5.x 中,生成器函数中的 return 只能用于终止生成器,不能带有值(return; 或省略 return 是可以的,但 return $value; 会导致语法错误)。
空生成器的 getReturn
<?php
declare(strict_types=1);
function emptyGenerator(): \Generator
{
return 'empty result';
// 没有 yield,生成器直接结束
}
$gen = emptyGenerator();
echo $gen->getReturn(); // empty result
// 如果既没有 yield 也没有 return
function trulyEmpty(): \Generator
{
// 空
}
$gen2 = trulyEmpty();
echo $gen2->getReturn(); // (null)return 值的类型
return 的值可以是任意类型,包括 null、标量类型、数组、对象等。在 PHP 8.1+ 中,你也可以使用枚举作为返回值。
<?php
declare(strict_types=1);
enum Status: string
{
case Success = 'ok';
case Partial = 'partial';
case Failed = 'failed';
}
function processData(iterable $data): \Generator
{
$hasError = false;
foreach ($data as $item) {
try {
yield transform($item);
} catch (\Throwable) {
$hasError = true;
}
}
return $hasError ? Status::Partial : Status::Success;
}最佳实践
- 利用返回值传递聚合信息:将生成器的最终统计、状态等通过
return返回,而不是修改外部变量。 - 在 finally 中确保返回值可用:即使生成器被中断,
getReturn()仍可获取return值。 - 文档化返回值类型:在 PHPDoc 中注明
return值的类型和含义。 - 与 yield from 配合使用:利用
yield from表达式捕获子生成器的返回值。 - 考虑使用专用结果对象:当返回值较复杂时,使用值对象(DTO)封装。
<?php
declare(strict_types=1);
readonly class ProcessingResult
{
public function __construct(
public int $processed,
public int $skipped,
public int $errors
) {}
public function isSuccess(): bool
{
return $this->errors === 0;
}
}
function processItems(iterable $items): \Generator
{
$stats = ['processed' => 0, 'skipped' => 0, 'errors' => 0];
foreach ($items as $item) {
// 处理逻辑...
yield $item;
$stats['processed']++;
}
return new ProcessingResult(
$stats['processed'],
$stats['skipped'],
$stats['errors']
);
}