Generator::send

(PHP 5 >= 5.5.0, PHP 7, PHP 8)

Generator::send向生成器发送值

描述

public Generator::send(混合 $value): 混合

将给定值发送到生成器,作为当前 yield 表达式的结果,并恢复生成器的执行。

如果在调用此方法时生成器不在 yield 表达式处,它将首先被允许前进到第一个 yield 表达式,然后再发送值。因此,没有必要用 Generator::next() 调用来“初始化” PHP 生成器(就像在 Python 中那样)。

参数

value

要发送到生成器的值。此值将是生成器当前所在的 yield 表达式的返回值。

返回值

返回产生的值。

示例

示例 #1 使用 Generator::send() 注入值

<?php
function printer() {
echo
"我是打印机!".PHP_EOL;
while (
true) {
$string = yield;
echo
$string.PHP_EOL;
}
}

$printer = printer();
$printer->send('Hello world!');
$printer->send('Bye world!');
?>

上面的例子将输出

I'm printer!
Hello world!
Bye world!

添加备注

用户贡献的备注 6 个备注

sfroelich01 at sp dot gm dot ail dot am dot com
11 年前
阅读示例,有点难以理解到底要怎么做。下面的例子是一个简单的例子,说明了你可以做什么。

<?php
function nums() {
for (
$i = 0; $i < 5; ++$i) {
//从调用者获取值
$cmd = (yield $i);

if(
$cmd == 'stop')
return;
//退出函数
}
}

$gen = nums();
foreach(
$gen as $v)
{
if(
$v == 3)//我们很满意
$gen->send('stop');

echo
"{$v}\n";
}

//输出
0
1
2
3
?>
php at didatus dot de
3 年前
如果你想在 foreach 循环中使用 generator::send(),你很可能会得到意想不到的结果。Generator::send() 方法会恢复生成器,这意味着生成器内的指针会移动到生成器列表中的下一个元素。

这里有一个例子

<?php

class ApiDummy
{
private static
$apiDummyData = ['a', 'b', 'c', 'd', 'e'];

public static function
getAll(): Generator {
foreach (
self::$apiDummyData as $entry) {
echo
'yielding $elem' . PHP_EOL;
$newElem = (yield $entry);
echo
'yield return: ' . $newElem . PHP_EOL;
}
}
}

$generator = ApiDummy::getAll();

// 示例迭代一,结果不符合预期
foreach ($generator as $elem) {
echo
'value from generator: ' . $elem . PHP_EOL;
$generator->send($elem . '+');
}

// 示例迭代二,结果符合预期
while ($generator->valid()) {
$elem = $generator->current();
echo
'value from generator: ' . $elem . PHP_EOL;
$generator->send($elem . '+');
}
?>

示例迭代一的結果
yielding $elem
value from generator: a
yield return: a+
yielding $elem
yield return
yielding $elem
value from generator: c
yield return: c+
yielding $elem
yield return
yielding $elem
value from generator: e
yield return: e+

正如你所看到的,b 和 d 没有被打印出来,也没有被 + 符号扩展。
foreach 循环接收第一个 yield,send 调用导致第一个循环内第二个 yield。因此,第二个循环已经接收了第三个 yield,以此类推。

为了避免这种情况,一个解决方案可能是使用 while 循环和 Generator::send() 方法来向前移动生成器光标,并使用 Generator::current() 方法来获取当前值。循环可以使用 Generator::valid() 方法来控制,该方法在生成器完成时返回 false。请参阅示例迭代二。

示例迭代二的预期结果
yielding $elem
value from generator: a
yield return: a+
yielding $elem
value from generator: b
yield return: b+
yielding $elem
value from generator: c
yield return: c+
yielding $elem
value from generator: d
yield return: d+
yielding $elem
value from generator: e
yield return: e+
anonymous at example dot com
5 年前
从 7.3 开始,生成器在 foreach 循环中的行为取决于它是否期望接收数据。如果您遇到“跳过”问题,这一点很重要。

<?php
class X implements IteratorAggregate {
public function
getIterator(){
yield from [
1,2,3,4,5];
}
public function
getGenerator(){
foreach (
$this as $j => $each){
echo
"getGenerator(): yielding: {$j} => {$each}\n";
$val = (yield $j => $each);
yield;
// ignore foreach's next()
echo "getGenerator(): received: {$j} => {$val}\n";
}
}
}
$x = new X;

foreach (
$x as $i => $val){
echo
"getIterator(): {$i} => {$val}\n";
}
echo
"\n";

$gen = $x->getGenerator();
foreach (
$gen as $j => $val){
echo
"getGenerator(): sending: {$j} => {$val}\n";
$gen->send($val);
}
?>

getIterator(): 0 => 1
getIterator(): 1 => 2
getIterator(): 2 => 3
getIterator(): 3 => 4
getIterator(): 4 => 5

getGenerator(): yielding: 0 => 1
getGenerator(): sending: 0 => 1
getGenerator(): received: 0 => 1
getGenerator(): yielding: 1 => 2
getGenerator(): sending: 1 => 2
getGenerator(): received: 1 => 2
getGenerator(): yielding: 2 => 3
getGenerator(): sending: 2 => 3
getGenerator(): received: 2 => 3
getGenerator(): yielding: 3 => 4
getGenerator(): sending: 3 => 4
getGenerator(): received: 3 => 4
getGenerator(): yielding: 4 => 5
getGenerator(): sending: 4 => 5
getGenerator(): received: 4 => 5
sergei dot solomonov at gmail dot com
10 年前
<?php
function foo() {
$string = yield;
echo
$string;
for (
$i = 1; $i <= 3; $i++) {
yield
$i;
}
}

$generator = foo();
$generator->send('Hello world!');
foreach (
$generator as $value) echo "$value\n";
?>

这段代码报错
PHP 致命错误: 未捕获的异常 'Exception',消息为 '无法重绕已经运行的生成器'。
foreach 内部调用 rewind,请记住这一点!
baohx2000 at gmail dot com
4 年前
我发现反向生成器(使用 $x = yield)是处理分块批处理的绝佳方式。当数据被迭代时,一旦特定数量的数据被馈送到生成器,它就会进行处理并重置数据。例如,您可以每 500 条记录进行一次批次 MySQL 插入。

示例(注意对 null 的处理,您将向生成器发送 null 以处理前一批次后的剩余数据)

function importer()
{
$max = 500;
$items = [];
while (true) {
$item = yield;
if ($item !== null) {
$items[] = yield;
}
if ($item === null || count($items) >= $max) {
// 进行批次操作
$items = [];
}
}
}
kexianbin at diyism dot com
8 年前
一个例子

$coroutine=call_user_func(create_function('', <<<'fun_code'
echo "inner 1:\n";
$rtn=(yield 'yield1');
echo 'inner 2:';var_export($rtn);echo "\n";
$rtn=(yield 'yield2');
echo 'inner 3:';var_export($rtn);echo "\n";
$rtn=(yield 'yield3');
echo 'inner 4:';var_export($rtn);echo "\n";
fun_code
));
echo ":outer 1\n"; // :outer 1
var_export($coroutine->current());echo ":outer 2\n"; // inner 1:, 'yield1':outer 2
var_export($coroutine->current());echo ":outer 3\n"; // 'yield1':outer 3
var_export($coroutine->next());echo ":outer 4\n"; // inner 2:NULL, NULL:outer 4
var_export($coroutine->current());echo ":outer 5\n"; // 'yield2':outer 5
var_export($coroutine->send('jack'));echo ":outer 6\n"; // inner 3:'jack', 'yield3':outer 6
var_export($coroutine->current());echo ":outer 7\n"; // 'yield3':outer 7
var_export($coroutine->send('peter'));echo ":outer 8\n"; // inner 4:'peter', NULL:outer 8
To Top