CakeFest 2024: The Official CakePHP Conference

Generator::send

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

Generator::send値をジェネレータに送る

説明

public Generator::send(mixed $value): mixed

指定した値を yield の結果としてジェネレータに送り、ジェネレータを続行します。

ジェネレータが yield 式を指していないときにこのメソッドが呼ばれると、 まずは最初の yield 式まで進めてから値を送信します。そのため、 最初のジェネレータを指すためにわざわざ Generator::next() を呼ぶ必要はありません (Python と同じです)。

パラメータ

value

ジェネレータに送り込む値。この値が、ジェネレータが現在指している yield 式の戻り値になります。

戻り値

yield した値を返します。

例1 Generator::send() による値の注入

<?php
function printer() {
echo
"I'm printer!".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!

add a note

User Contributed Notes 6 notes

up
70
sfroelich01 at sp dot gm dot ail dot am dot com
10 years ago
Reading the example, it is a bit difficult to understand what exactly to do with this. The example below is a simple example of what you can do this.

<?php
function nums() {
for (
$i = 0; $i < 5; ++$i) {
//get a value from the caller
$cmd = (yield $i);

if(
$cmd == 'stop')
return;
//exit the function
}
}

$gen = nums();
foreach(
$gen as $v)
{
if(
$v == 3)//we are satisfied
$gen->send('stop');

echo
"{$v}\n";
}

//Output
0
1
2
3
?>
up
9
php at didatus dot de
2 years ago
If you want to use generator::send() within a foreach loop, you will most likely get an unexpected result. The Generator::send() method resumes the generator, which means the pointer within the generator is moved to the next element in the generator list.

Here is an example:

<?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();

// example iteration one with unexpected result
foreach ($generator as $elem) {
echo
'value from generator: ' . $elem . PHP_EOL;
$generator->send($elem . '+');
}

// example iteration two with the expected result
while ($generator->valid()) {
$elem = $generator->current();
echo
'value from generator: ' . $elem . PHP_EOL;
$generator->send($elem . '+');
}
?>

The result of example iteration one:
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+

As you can see, the values b and d are not printed out and also not extended by the + sign.
The foreach loop receives the first yield and the send call causes a second yield within the first loop. Therefor the second loop already receives the third yield and so on.

To avoid this, one solution could be to use a while loop and the Generator::send() method to move the generator cursor forward and the Generator::current() method to retrieve the current value. The loop can be controlled with the Generator::valid() method which returns false, if the generator has finished. See example iterator two.

The expected result of example iteration two:
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+
up
4
anonymous at example dot com
4 years ago
As of 7.3, the behavior of a generator in a foreach loop depends on whether or not it expects to receive data. Relevant if you are experiencing "skips".

<?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
up
10
sergei dot solomonov at gmail dot com
10 years ago
<?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";
?>

This code falls with the error:
PHP Fatal error: Uncaught exception 'Exception' with message 'Cannot rewind a generator that was already run'.
foreach internally calls rewind, you should remember this!
up
0
baohx2000 at gmail dot com
4 years ago
I have found that inverse generators (using $x = yield) is a great way to handle chunked batch processing. As data is being iterated, once a specific count has been fed to the generator, it processes and resets the data. For example, you could do a batch mysql insert every 500 records.

Example (note the handling of null, which you would send to the generator to handle stragglers after the previous batch)

function importer()
{
$max = 500;
$items = [];
while (true) {
$item = yield;
if ($item !== null) {
$items[] = yield;
}
if ($item === null || count($items) >= $max) {
// do batch operations
$items = [];
}
}
}
up
-14
kexianbin at diyism dot com
8 years ago
an example:

$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