David Rodrigues
David Rodrigues

Reputation: 12542

"yield" and "yield from" at same function

I need use yield and yield from at same function, but it seems not works as intended, once that it yields only the last yield from or yield (what comes last).

My code is (https://3v4l.org/jFDXh):

function yieldItems() {
    yield 1;
    yield 2;
    yield 3;
    yield from [4, 5, 6];
    yield from [7, 8, 9];
}

var_dump(
    iterator_to_array(
        yieldItems()
    )
);

For all PHP versions it will only outputs [ 7, 8, 9 ], but seems clear to me that it should be [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] instead.

However, if I do the same through a foreach, everything seems normal. It looks like it is a problem related to iterator_to_array(), but in fact, I need to convert this Generator to an array.

So, what I am missing?

Upvotes: 4

Views: 216

Answers (1)

David Rodrigues
David Rodrigues

Reputation: 12542

When I was concluding, I realized that the problem was really related to iterator_to_array(), so I decided to search more deeply in the PHP Documentation / Generators and it shows precisely this type of problem.

Following the documentation:

yield from does not reset the keys. It preserves the keys returned by the Traversable object, or array. Thus some values may share a common key with another yield or yield from, which, upon insertion into an array, will overwrite former values with that key.

A common case where this matters is iterator_to_array() returning a keyed array by default, leading to possibly unexpected results. iterator_to_array() has a second parameter use_keys which can be set to FALSE to collect all the values while ignoring the keys returned by the Generator.

What this means, in general, is that when using yield/yield from, it will outputs keys too (the first yield will be 0, for instance), just as it actually exists for pure arrays. So the code below will fail similarly (https://3v4l.org/pWeWT):

function willOutputSingle() {
    yield 'sameKey' => 'originalValue';
    yield 'sameKey' => 'otherValue';
}

var_dump(
    iterator_to_array(
        willOutputSingle()
    )    
); 

// Outputs: [ 'sameKey' => 'otherValue' ]

It will happen because we yields to pairs like [ 'sameKey' => 'originalValue' ] and then [ 'sameKey' => 'otherValue' ], so when we converts it to array, via iterator_to_array() the results is basically that:

[ 'sameKey' => 'originalValue',
  'sameKey' => 'otherValue ]

But how PHP does not allows identical keys, only the last one is preserved.

The solution to this is pass false as second argument to iterator_to_array(), because it will not preserve keys yielded, so result will be an zero-index array. The result will be:

[ 0 => 'originalValue',
  1 => 'otherValue' ]

Upvotes: 3

Related Questions