Generator functions should have a return type of \Generator
or a supertype†.
Generator functions don't "return" what they're yielding, they "return" a "yielder" if you want to think about it like that. I believe that much of the confusion stems from referring to generator functions as generators.
A better way to think about it is that a generator function outputs a Generator
object.
So we have two terms:
Another confusing aspect is that we use the return type syntax to denote that the function will output a Generator
. In reality nothing can be literally return
ed from a generator function. The return;
statement in the context of a generator function is more like an exit;
for the function scope.
(The pedantic amongst you best not mention the analogous values returned by exit(1)
in this discussion. 🙏 )
I think this example demonstrates all of this decently:
function test(): \Generator {
yield 'test';
return "This text vanishes into the abyss."; // return values aren't a thing
return; // instead, this "void" is the only return statement that makes sense
yield 'Execution never reaches this text.';
}
$test = test(); // this is a \Generator object
foreach($test as $item) { // \Generator objects are iterable
echo $item; // prints 'test' and nothing else
}
† In your specific case (setting the return type to iterator
) the code works fine, presumably because a Generator
is some form of iterator
implementation. The PHP manual and errors use the word supertype to describe allowed return types, but I guess iterator
(which I consider a subtype) applies as well.
By thinking of this iterator
as more of an "iterable" interface, it might be more clear what generator functions' return type should be: Something you can iterate over, which is most accurately, a Generator
object.
I recommend against using the return type iterator
for a generator function since it's not as clear and it may obfuscate typos in the function until runtime. I won't spell out an edge case example, but you can "smell" it. Anyway, it's a simple rule for me: If a function contains yield;
I'm going to return type it \Generator
.
Trivia:
- The
yield
statement itself is what makes the function a generator function. This means that if you use yield
anywhere in the function, executed or not, the function will be "compiled" as a generator function. (This doesn't include // yield
in comments of course.) This means that declaring a return type isn't required for a generator function, but as always, is best practice.
else { yield from $collection->items; };
– Soakage