PHP 8.1.0 Released!

Generator::send

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

Generator::sendEnvoi une valeur au générateur

Description

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

Envoi la valeur fournie au générateur comme résultat de l'expression courante yield, et reprend l'exécution du générateur.

Si le générateur n'est pas une expression yield lorsque cette méthode est appelée, il va d'abord avancer à la première expression yield avant d'envoyer la valeur. Ainsi, il n'est pas nécessaire de démarrer un générateur PHP avec un appel à la méthode Generator::next() (comme c'est le cas en Python).

Liste de paramètres

value

Valeur à envoyer au générateur. Cette valeur sera la valeur retournée de l'expression yield de la position courante du générateur.

Valeurs de retour

Retourne la valeur cédée.

Exemples

Exemple #1 Exemple d'utilisation Generator::send() pour injecter des données

<?php
function printer() {
    echo 
"I'm printer!".PHP_EOL;
    while (
true) {
        
$string = yield;
        echo 
$string;
    }
}

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

L'exemple ci-dessus va afficher :

I'm printer!
Hello world!
Bye world!

add a note add a note

User Contributed Notes 6 notes

up
62
sfroelich01 at sp dot gm dot ail dot am dot com
8 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
sergei dot solomonov at gmail dot com
8 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
1
baohx2000 at gmail dot com
2 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
1
anonymous at example dot com
2 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
1
php at didatus dot de
6 months 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
-11
kexianbin at diyism dot com
6 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