Understanding PHP's "yield" and "yield from" directives
Published on February 14th, 2026
Assume we have 2 methods that return a \Generator instance with 5 items each, and we merge them using yield from in another function called getData:
1$result = iterator_to_array(getData());
2
3var_dump(count($result));
4
5function getData(): \Generator
6{
7 yield from getFoo();
8 yield from getBar();
9}
10
11function getFoo(): \Generator
12{
13 foreach (range(0, 4) as $number) {
14 yield 'foo:'.$number;
15 }
16}
17
18function getBar(): \Generator
19{
20 foreach (range(0, 4) as $number) {
21 yield 'bar:'.$number;
22 }
23}
What do you think the output of the var_dump at the top of the code snippet will be? I'll give you a little bit to work this out in your head.
...
If you guessed 10 like I did, you'd be wrong! It's actually 5. Let me try to explain why we end up with this result. As it turns out, both getFoo and getBar functions yield the following key-value pairs (mind the numeric keys):
1[
2 0 => '<name>:0',
3 1 => '<name>:1',
4 2 => '<name>:2',
5 3 => '<name>:3',
6 4 => '<name>:4',
7]
If we then flatten them into a single generator using yield from like we're doing in getData, you effectively end up with this generator:
1[
2 0 => 'foo:0',
3 1 => 'foo:1',
4 2 => 'foo:2',
5 3 => 'foo:3',
6 4 => 'foo:4',
7 0 => 'bar:0', // <-- Duplicate key `0`
8 1 => 'bar:1', // <-- Duplicate key `1`
9 2 => 'bar:2', // <-- Duplicate key `2`
10 3 => 'bar:3', // <-- Duplicate key `3`
11 4 => 'bar:4', // <-- Duplicate key `4`
12]
Duplicate keys are allowed in generators (because there's no concept of "keys"). When the generator is then converted to an array where duplicate keys are not allowed, the last ones take precedence and you end up with this:
1[
2 0 => 'bar:0',
3 1 => 'bar:1',
4 2 => 'bar:2',
5 3 => 'bar:3',
6 4 => 'bar:4',
7]
Learning:
yield frompreserves keys from yielded items, whereas a plainyield(without an explicit key) uses sequential numeric keys.
To prevent this, you can either rewrite your getData function to yield every single item separately, which does not use the key of the yielded item:
1function getData(): \Generator
2{
3 foreach (getFoo() as $foo) {
4 yield $foo;
5 }
6
7 foreach (getBar() as $bar) {
8 yield $bar;
9 }
10}
Or loop over the resulting generator to reset the array keys:
1function getData(): \Generator
2{
3 $generator = (function () {
4 yield from getFoo();
5 yield from getBar();
6 })();
7
8 foreach ($generator as $item) {
9 yield $item;
10 }
11}
If you're using Laravel's LazyCollection class, you can also work around this by re-indexing the generator with a call to ->values():
1LazyCollection::make(function () {
2 yield from getFoo();
3 yield from getBar();
4})->values();
This will loop over all the items in the generator and yield everything again, essentially doing what I did in my second solution above.