A normal function builds a whole result and returns it. A generator produces values one at a time, pausing between them. It uses the keyword yield.
<?php
function countTo(int $max): Generator
{
for ($i = 1; $i <= $max; $i++) {
yield $i;
}
}
foreach (countTo(3) as $number) {
echo $number . " ";
}
1 2 3
When the loop asks for the next value, PHP runs the function until the next yield, hands over the value and pauses. The state (the variable $i) is remembered until the next request.
Why generators matter: memory
Imagine reading a file with a million lines. Loading it into an array needs memory for every line. A generator gives one line at a time, so memory stays small.
<?php
function readLines(string $file): Generator
{
$handle = fopen($file, "r");
try {
while (($line = fgets($handle)) !== false) {
yield rtrim($line, "\r\n");
}
} finally {
fclose($handle);
}
}
file_put_contents("big.txt", "alpha\nbeta\ngamma\n");
foreach (readLines("big.txt") as $number => $line) {
echo ($number + 1) . ": $line\n";
}
1: alpha
2: beta
3: gamma
The finally makes sure the file is closed even if the loop stops early.
Compare the memory
<?php
function rangeArray(int $n): array
{
return range(1, $n);
}
function rangeGenerator(int $n): Generator
{
for ($i = 1; $i <= $n; $i++) {
yield $i;
}
}
$before = memory_get_usage();
$sum = 0;
foreach (rangeGenerator(100000) as $i) {
$sum += $i;
}
$used = memory_get_usage() - $before;
echo "Sum: $sum\n";
echo $used < 100000 ? "The generator used very little memory" : "More memory than expected";
Sum: 5000050000
The generator used very little memory
Keys
<?php
function pairs(): Generator
{
yield "a" => 1;
yield "b" => 2;
}
foreach (pairs() as $key => $value) {
echo "$key=$value ";
}
a=1 b=2
yield from: delegate to another generator
<?php
function inner(): Generator
{
yield 1;
yield 2;
}
function outer(): Generator
{
yield 0;
yield from inner();
yield 3;
}
echo implode(", ", iterator_to_array(outer(), false));
0, 1, 2, 3
The second argument false tells iterator_to_array() to ignore keys, which would otherwise repeat.
Pipelines
Generators can be chained, and each step handles one item at a time.
<?php
function numbers(): Generator
{
foreach (range(1, 10) as $n) {
yield $n;
}
}
function onlyEven(iterable $source): Generator
{
foreach ($source as $n) {
if ($n % 2 === 0) {
yield $n;
}
}
}
function squared(iterable $source): Generator
{
foreach ($source as $n) {
yield $n * $n;
}
}
echo implode(", ", iterator_to_array(squared(onlyEven(numbers())), false));
4, 16, 36, 64, 100
Sending values in
A generator can also receive values with ->send(). It is used less often, in coroutines.
Limits
- A generator can be looped over once. To loop again, call the function again.
- You cannot
count()a generator or jump to an item. It only moves forward. - You cannot
rewind()after it has started.
When to use a generator
- Large files or large database results.
- Endless or very long sequences.
- Building a lazy pipeline of steps.
For small data, a plain array is simpler and just as fast.
Return type Generator
: Generator (or : iterable) on functions that use yield. A function with yield always returns a generator object, even if the yield is never reached.