Generator::send
(PHP 5 >= 5.5.0)
Generator::send — Send a value to the generator
Description
Sends the given value to the generator as the result of the current yield expression and resumes execution of the generator.
If the generator is not at a yield expression when this method is called, it will first be let to advance to the first yield expression before sending the value. As such it is not necessary to "prime" PHP generators with a Generator::next() call (like it is done in Python).
Parameters
-
value
-
Value to send into the generator. This value will be the return value of the yield expression the generator is currently at.
Examples
Example #1 Using Generator::send() to inject values
<?php
function printer() {
while (true) {
$string = yield;
echo $string;
}
}
$printer = printer();
$printer->send('Hello world!');
$printer->send('Bye world!');
?>
The above example will output:
Hello world! Bye world!
Return Values
Returns the yielded value.
- PHP Руководство
- Функции по категориям
- Индекс функций
- Справочник языка
- Встроенные интерфейсы и классы
- Функция Generator::current() - Get the yielded value
- Generator::getReturn
- Функция Generator::key() - Get the yielded key
- Функция Generator::next() - Resume execution of the generator
- Функция Generator::rewind() - Rewind the iterator
- Функция Generator::send() - Send a value to the generator
- Функция Generator::throw() - Throw an exception into the generator
- Функция Generator::valid() - Check if the iterator has been closed
- Функция Generator::__wakeup() - Serialize callback
Коментарии
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
?>
<?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!
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
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 = [];
}
}
}
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+