phpgeneratorphp-5.5yield-keyword

What does yield mean in PHP?


I've recently stumbled over this code:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

I've never seen this yield keyword before. Trying to run the code I get

Parse error: syntax error, unexpected T_VARIABLE on line x

So what is this yield keyword? Is it even valid PHP? And if it is, how do I use it?


Solution

  • What is yield?

    The yield keyword returns data from a generator function:

    The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.

    What is a generator function?

    A generator function is effectively a more compact and efficient way to write an Iterator. It allows you to define a function (your xrange) that will calculate and return values while you are looping over it:

    function xrange($min, $max) {
        for ($i = $min; $i <= $max; $i++) {
            yield $i;
        }
    }
    
    […]
    
    foreach (xrange(1, 10) as $key => $value) {
        echo "$key => $value", PHP_EOL;
    }
    

    This would create the following output:

    0 => 1
    1 => 2
    …
    9 => 10
    

    You can also control the $key in the foreach by using

    yield $someKey => $someValue;
    

    In the generator function, $someKey is whatever you want appear for $key and $someValue being the value in $val. In the question's example that's $i.

    Please note that, internally, sequential integer keys are paired with the yielded values, just as with a non-associative array. We can even set yield values with keys.

    What's the difference to normal functions?

    Now you might wonder why we are not simply using PHP's native range function to achieve that output. And right you are. The output would be the same. The difference is how we got there.

    When we use range PHP, will execute it, create the entire array of numbers in memory and return that entire array to the foreach loop which will then go over it and output the values. In other words, the foreach will operate on the array itself. The range function and the foreach only "talk" once. Think of it like getting a package in the mail. The delivery guy will hand you the package and leave. And then you unwrap the entire package, taking out whatever is in there.

    When we use the generator function, PHP will step into the function and execute it until it either meets the end or a yield keyword. When it meets a yield, it will then return whatever is the value at that time to the outer loop. Then it goes back into the generator function and continues from where it yielded. Since your xrange holds a for loop, it will execute and yield until $max was reached. Think of it like the foreach and the generator playing ping pong.

    Why do I need that?

    Obviously, generators can be used to work around memory limits. Depending on your environment, doing a range(1, 1000000) will fatal your script whereas the same with a generator will just work fine. Or as Wikipedia puts it:

    Because generators compute their yielded values only on demand, they are useful for representing sequences that would be expensive or impossible to compute at once. These include e.g. infinite sequences and live data streams.

    Generators are also supposed to be pretty fast. But keep in mind that when we are talking about fast, we are usually talking in very small numbers. So before you now run off and change all your code to use generators, do a benchmark to see where it makes sense.

    Another Use Case for Generators is asynchronous coroutines. The yield keyword does not only return values but it also accepts them. For details on this, see the two excellent blog posts linked below.

    Since when can I use yield?

    Generators have been introduced in PHP 5.5. Trying to use yield before that version will result in various parse errors, depending on the code that follows the keyword. So if you get a parse error from that code, update your PHP.

    Sources and further reading: