In PHP, what does yield from do with an inner generator's values, keys and return value?
answer
- delegates to a generator, Traversable or array
- inner keys pass through unchanged
- outer auto-keys keep their own counter
- expression evaluates to the inner return
- a bare helper() call yields nothing
basics
~20 syield from hands every value of an inner generator, Traversable or array to the outer generator's consumer with the inner keys unchanged, and the yield from expression evaluates to the inner generator's return value. Duplicate keys therefore appear easily.
solid answer
~50 s`yield from $inner` makes the outer generator pass through every value its operand produces, where the operand may be another generator, any `Traversable`, or an array; non-iterables throw `Error`. Keys are **not renumbered**: the consumer sees the inner keys, while the outer generator's own automatic keys keep counting from where they were, so `yield 0; yield from inner(); yield 4;` produces keys 0, 0, 1, 2, 1. Collecting that with `iterator_to_array()` keeps keys by default, so later values overwrite earlier ones. When the operand is a generator, the `yield from` expression evaluates to that generator's `return` value; for arrays and other `Traversable` it is `null`. While delegating, `send()` and `throw()` on the outer generator reach the inner one. A common bug is calling a helper generator without `yield from`: that only creates a `Generator` nobody iterates.
code
php · 21 lines<?php
function inner(): Generator
{
yield 1;
yield 2;
return 'inner done';
}
function outer(): Generator
{
yield 0;
$result = yield from inner();
echo $result, "\n"; // inner done
yield 3;
}
foreach (outer() as $k => $v) {
echo "$k=$v "; // 0=0 0=1 1=2, then inner done, then 1=3
}
var_dump(iterator_to_array(outer(), false)); // [0, 1, 2, 3]go deeper
Recall that yield from passes on every value of another generator, array or Traversable before the outer generator continues.
Explain that inner keys pass through unchanged, why that collides in iterator_to_array(), and that the expression evaluates to the inner generator's return value.
Catch the silent bug of calling a helper generator without yield from, and design recursive or concatenated streams with keys the consumer can trust.
Weigh building data pipelines from composed generators against explicit collection objects, considering debuggability, key semantics and how callers will consume them.
## Delegation in one keyword `yield from` lets one generator hand part of its work to another iterable. The outer generator pauses at the `yield from` expression, and until the inner iterable is exhausted every value the consumer pulls comes from the inner one. After that, the outer generator continues with its next statement. The operand may be: - **another generator**, the common case, for splitting a long generator into smaller helpers or for recursion over trees; - **any `Traversable`** object, such as an `ArrayIterator`; - **an array**, which is walked element by element. Anything else, such as a plain object that is not traversable, throws `Error` with *Can use "yield from" only with arrays and Traversables*. ## Keys pass through unchanged This is the part interviewers probe. `yield from` does **not** renumber keys. The consumer receives exactly the keys the inner iterable produces. Meanwhile, the outer generator keeps its own automatic-key counter, and delegation does not advance it. ```php function inner() { yield 1; yield 2; yield 3; } // keys 0, 1, 2 function outer() { yield 0; // key 0 (outer counter) yield from inner(); // keys 0, 1, 2 (inner's own) yield 4; // key 1 (outer counter resumes) } ``` A `foreach ($gen as $k => $v)` sees the pairs 0=>0, 0=>1, 1=>2, 2=>3, 1=>4. That is harmless in a loop that ignores keys, but anything that uses keys as array indexes collides: | Consumer | Result for `outer()` | |---|---| | `foreach` reading only values | 0 1 2 3 4, as expected | | `iterator_to_array(outer())` (keys preserved by default) | `[0 => 1, 1 => 4, 2 => 3]`: later values overwrote earlier ones | | `iterator_to_array(outer(), false)` | `[0, 1, 2, 3, 4]`: keys discarded | The PHP manual calls out this exact case. The fix is either to ignore keys when collecting, or to make keys meaningful (for example yield explicit, unique keys) when the consumer relies on them. ## The expression value is the inner return Since PHP 7.0 a generator may `return` a value, which its consumer reads with `getReturn()`. Inside a delegating generator, the `yield from` expression itself evaluates to that value: 1. The outer generator reaches `$total = yield from countRows($file);`. 2. All of `countRows()`'s yielded values flow to the consumer. 3. When `countRows()` executes `return 1234;`, the expression evaluates to 1234 and the outer generator continues with `$total` set. For an array or a non-generator `Traversable` there is no return value, so the expression is `null`. ## Communication passes through While the outer generator is delegating, the consumer is effectively talking to the inner generator: - `current()` and `key()` report the inner generator's current value and key; - `send($v)` delivers `$v` to the `yield` the inner generator is paused at; - `throw($e)` raises `$e` inside the inner generator, where it may be caught; if it is not, it propagates up into the outer generator at the `yield from` line. ## The forgotten yield from The most common delegation defect has no `yield from` at all: ```php function all(): Generator { yield 'header'; rows(); // rows() contains yield yield 'footer'; } ``` Because `rows()` contains `yield`, calling it only creates a `Generator`. That object is discarded, its body never runs, and the consumer sees `header` then `footer`. No error or warning appears at runtime; the fix is the keyword: `yield from rows();`. ## When to reach for it Use `yield from` to split a long generator into named helpers, to walk recursive structures (a directory tree, nested categories) by delegating to the same function for each child, and to concatenate several sources into one lazy stream.
- How would you walk a nested category tree lazily with yield from?Write one generator that yields the current node and then, for each child, does `yield from walk($child)`. Recursion delegates to a new generator per level, values flow straight to the consumer, and only the path from root to the current node is held in memory. Yield explicit keys, such as category ids, if the consumer will index by key.
- What happens if the inner generator throws while the outer one is delegating to it?The exception propagates out of the inner generator into the outer one at the `yield from` line. The outer generator can catch it with try/catch around the `yield from`; if it does not, the exception continues to whoever pulled the value.
saying these in an interview costs you the question
- yield from renumbers the inner generator's keys to follow the outer ones.
- Calling a helper generator without yield from still yields its values.
- yield from only accepts other generators, not arrays or iterators.
- The yield from expression evaluates to the last value the inner generator yielded.
- iterator_to_array() discards keys by default, so duplicate keys are harmless.