Use of generators with while results in infinite loop

Asked

Viewed 46 times

3

I made a small example of using generators to better understand how it works in php.

the method getValues is an example of values that would come from a database or any other data source that returns an array.

using the loop while with a "control" variable called $iterator the result is an infinite loop, but if I use the foreach works smoothly, which is wrong?

<?php


$iterator = 0;

function getValues(&$iterator){

    while($iterator < 1) {
        $iterator++;
        yield ['valor1', 'valor2'];
    }
}

while($values = getValues($iterator)) {

    var_dump($iterator);
    $values = iterator_to_array($values);
    print_r($values);
}

1 answer

3


When you use yield, is creating a Generator Function, and according to the documentation, her return is always an object of the type Generator. We can see that changing your code a little bit:

function getValues(&$iterator){
    while($iterator < 1) {
        $iterator++;
        yield ['valor1', 'valor2'];
    }
}

$iterator = 0;

echo "primeira chamada\n";
$values = getValues($iterator);
var_dump($values);
if ($values) {
    print_r(iterator_to_array($values));
} else {
    echo "generator terminou";
}

echo "\nsegunda chamada\n";
$values = getValues($iterator);
var_dump($values);
if ($values) {
    print_r(iterator_to_array($values));
} else {
    echo "generator terminou";
}

echo "\nterceira chamada\n";
$values = getValues($iterator);
var_dump($values);
if ($values) {
    print_r(iterator_to_array($values));
} else {
    echo "generator terminou";
}

Instead of a loop, I called getValues several times to see what happens with each iteration. The output is:

primeira chamada
object(Generator)#8 (0) {
}
Array
(
    [0] => Array
        (
            [0] => valor1
            [1] => valor2
        )

)

segunda chamada
object(Generator)#10 (0) {
}
Array
(
)

terceira chamada
object(Generator)#8 (0) {
}
Array
(
)

That is, on the first call it returns the array and from the second on, it has no value anymore, because it has already ended.

But getValues always returns the object Generator, and this is always evaluated as true. That’s why your loop never ends.


The ideal is to iterate with a foreach even then it automatically closes when the Generator break up.

But if you want to do it with while, one way is to check whether a Generator already "finished", using the method valid:

function getValues(&$iterator){
    while($iterator < 1) {
        $iterator++;
        yield ['valor1', 'valor2'];
    }
}

$iterator = 0;

echo "primeira chamada\n";
$values = getValues($iterator);
var_dump($values);
if ($values->valid()) { // <--- aqui
    print_r(iterator_to_array($values));
} else {
    echo "generator terminou";
}

echo "\nsegunda chamada\n";
$values = getValues($iterator);
var_dump($values);
if ($values->valid()) { // <--- aqui
    print_r(iterator_to_array($values));
} else {
    echo "generator terminou";
}

echo "\nterceira chamada\n";
$values = getValues($iterator);
var_dump($values);
if ($values->valid()) { // <--- aqui
    print_r(iterator_to_array($values));
} else {
    echo "generator terminou";
}

Now he’s no longer in if from the second iteration, as there are no more values in the Generator. The exit is:

primeira chamada
object(Generator)#8 (0) {
}
Array
(
    [0] => Array
        (
            [0] => valor1
            [1] => valor2
        )

)

segunda chamada
object(Generator)#10 (0) {
}
generator terminou
terceira chamada
object(Generator)#8 (0) {
}
generator terminou

So in your case you could use a loop and only interrupt it when the Generator finish (using valid to find out if you’re finished):

while(1) {
    $values = getValues($iterator);
    if (! $values->valid()) break; // generator terminou, interrompe o while
    var_dump($iterator);
    $values = iterator_to_array($values);
    print_r($values);
}

Or do it all at once in the condition of while:

while(($values = getValues($iterator))->valid()) {
    var_dump($iterator);
    $values = iterator_to_array($values);
    print_r($values);
}

Although you don’t have to keep calling getValues all the time:

$gen = getValues($iterator);
while ($gen->valid()) {
    $values = $gen->current(); // pega o valor atual
    var_dump($iterator);
    print_r($values);
    $gen->next(); // avança para o próximo
}

But I still prefer to use the foreach even:

foreach (getValues($iterator) as $values) {
    var_dump($iterator);
    print_r($values);
}
  • 2

    thank you very much for the detailed explanation!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.