26
loading...
This website collects cookies to deliver better user experience
iterable
instead of array
type-hinting ever since I learned about them.return
a single value or even void
, a generator can return multiple results. The only thing you have to do to change a function into a generator is to replace return
with yield
and call it.iterable
, meaning you have to loop over them in order to retrieve the results. You can simply foreach
over a generator, and it will return every yield
it encounters.function exampleGenerator() {
yield 1;
yield 2;
yield 3;
}
$generator = exampleGenerator();
foreach ($generator as $value) {
echo $value;
}
// will echo out: 123
$generator
variable. You might accidentally try to iterate over that.$generator = function() {
yield 1;
yield 2;
yield 3;
};
// Incorrect: $generator is now an uncalled function.
foreach($generator as $value) // ...
// Correct: $generator() is now a `Generator` object.
foreach($generator() as $value) // ...
ChoiceField
-object that has array $options
, and you have to retrieve the options from a database. When the field is rendered, it obviously needs to show those options. But when those options aren't rendered in that request, the database call will still be performed to instantiate the field.array $options
into iterable $options
and provide the options via a generator, the database call will only ever be executed if you foreach
over those options.$options = function() {
foreach(DB::query('retrieve the options') as $option) {
yield $option;
}
};
$field = new ChoiceField($options());
Tip: If you already have an iterable result set, like an array
or any other iterable
, you can use yield from $resuls
. This will in essence foreach
over all the results and yield
every one of them.
// Use `yield from` instead of looping the results.
$options = (function() {
yield from DB::query('retrieve the options');
})(); // Notice we called the function directly to return the generator.
// Or shorthand
$options = (fn() => yield from DB::query('retrieve the options'))();
$options = (function() {
$results = DB::query('retrieve the options');
foreach($results as $result) {
// This way there is only one `Option` in memory at all times.
yield Option::createFromResult($result);
}
unset($results);
})();
yield
the result, we build up the Option
model that represents that result. This saves a lot of memoryunset($results)
after we returned the results. This is because the generator will keep going until it no longer yields any results, unlike a return
statement where the function will end immediately. That's pretty awesome. This way you can even clean up some left over memory consumption after your generator finishes.yield
a result, there is an implicit numeric 0-based key iterating the result. You can however yield both a key and a value by adding the =>
arrow.// Without keys.
function fruits() {
yield 'apple';
yield 'banana';
yield 'peach';
}
foreach(fruits() as $key => $fruit) ... // Here key will be 0, 1, 2
// With keys.
function fruits() {
yield 'zero' => 'apple';
yield 'one' => 'banana';
yield 'two' => 'peach';
yield 'two' => 'lime';
}
foreach(fruits() as $key => $fruit) // Here $key will be 'zero',' one', 'two', 'two'
iterator_to_array()
the key would be there only once, holding the last result for that key.array
type. This means you can run into these caveats.array_
functions all require an actual array. So you cannot for example simply call array_map()
with your generator. To remedy this, you can use iterator_to_array()
to turn your generator into an array. This will however reintroduce the memory usage of arrays.Tip: You might use iterator_apply
to preform a callback on the yielded result, but this is not recommended as this function does not return an iterator itself or any of the results. It only performs a callback for every iteration, but the callback doesn't receive the result. You have to provide the iterator as an argument, and you can then retrieve the current()
iteration. It's not worth it.
yield
as many results as we want, and the generator only has one reference in memory at a time, it's not possible to count the results without traversing them. To ease this process you can use iterator_count()
. This will loop over every result and return the actual count.Cannot traverse an already closed generator
.iterator_count()
also closes the iterator, so you can't do a count and then loop. You should probably just keep a record of the count while iterating.