0

Generators — yield, yield from, send(), getReturn()

Advanced5 min read·php-06-014
interviewcompareperformance

Concept

Generators extend beyond basic yield to support bidirectional communication (send()), delegation (yield from), return values (getReturn()), and sending exceptions into the generator (throw()).

send($value): Sends a value INTO the running generator. The yield expression inside the generator evaluates to the sent value. The first call to next() (or foreach stepping to the first item) runs until the first yield. Subsequent send() calls resume from that yield, and the sent value becomes the result of the yield expression. send() also advances the generator to the next yield and returns the newly yielded value.

yield from $iterable: Delegates to another generator (or any traversable). The outer generator yields all values from the inner one. The inner generator's return value becomes the result of the yield from expression. This enables generator composition and recursive generators without manually iterating sub-generators.

getReturn(): Retrieves the value from a generator's return statement after it has completed. Throws \Exception if the generator hasn't finished.

throw($exception): Injects an exception into the generator at the current suspension point. Used for error propagation in generator-based coroutines.

Coroutines: PHP generators can implement cooperative multitasking when combined with send() and an event loop. This is how async PHP libraries (like ReactPHP's coroutine support) are built.

Code Example

php
<?php
declare(strict_types=1);

// Bidirectional communication with send()
function logger(): Generator
{
    $messages = [];
    while (true) {
        $message = yield count($messages); // yield: count; receive: message
        if ($message === null) break;
        $messages[] = $message;
    }
    return $messages;
}

$log = logger();
$log->current();               // prime the generator (run to first yield)
$count = $log->send('First message');  // sends string, receives new count (1)
$count = $log->send('Second message'); // 2
$log->send(null);              // terminate
$allMessages = $log->getReturn(); // ['First message', 'Second message']

// yield from — delegation
function innerGen(): Generator
{
    yield 1;
    yield 2;
    return 'inner done';
}

function outerGen(): Generator
{
    yield 0;
    $innerResult = yield from innerGen(); // delegates, gets return value
    echo "Inner returned: $innerResult\n"; // "inner done"
    yield 3;
}

foreach (outerGen() as $val) {
    echo $val . "\n"; // 0, 1, 2, 3
}

// Recursive generator with yield from
function flatten(array $arr): Generator
{
    foreach ($arr as $item) {
        if (is_array($item)) {
            yield from flatten($item); // recursion via delegation
        } else {
            yield $item;
        }
    }
}
$nested = [1, [2, [3, 4], 5], 6];
foreach (flatten($nested) as $val) {
    echo $val . " "; // 1 2 3 4 5 6
}

// Memory-efficient range with getReturn
function rangeWithSum(int $n): Generator
{
    $sum = 0;
    for ($i = 1; $i <= $n; $i++) {
        $sum += $i;
        yield $i;
    }
    return $sum;
}
$gen = rangeWithSum(100);
foreach ($gen as $val) { /* iterate */ }
echo $gen->getReturn(); // 5050