Generators
A regular function first builds the whole result and then returns it at once. For a million rows that is a million items in memory. A generator hands out values one at a time — exactly when they are asked for. It needs only as much memory as one item takes.
yield
A function with the word yield becomes a generator. Each yield gives out one value and pauses the function until the next request:
<?php
function numbers()
{
yield 1;
yield 2;
yield 3;
}
foreach (numbers() as $number) {
echo $number, ' ';
}This prints 1 2 3. A generator is looped over with foreach, like an array.
Values on demand
A generator's code does not run all at once but step by step — between the iterations of the loop:
<?php
function steps()
{
echo '[start] ';
yield 'A';
echo '[middle] ';
yield 'B';
echo '[end]';
}
foreach (steps() as $step) {
echo $step, ' ';
}This prints [start] A [middle] B [end]: the function continues from where it stopped.
A generator instead of an array
A range of numbers without creating an array:
<?php
function countTo(int $limit)
{
for ($i = 1; $i <= $limit; $i++) {
yield $i;
}
}
$sum = 0;
foreach (countTo(1000000) as $number) {
$sum += $number;
}
echo $sum; // 500000500000An array of a million numbers would take tens of megabytes. The generator keeps only the current value.
Keys
yield can give out a key-value pair:
<?php
function prices()
{
yield 'tea' => 250;
yield 'coffee' => 400;
}
foreach (prices() as $name => $price) {
echo $name, ': ', $price, "\n";
}Stopping on a condition
A generator can be infinite — whoever uses it decides when to stop:
<?php
function evenNumbers()
{
$number = 0;
while (true) {
$number += 2;
yield $number;
}
}
foreach (evenNumbers() as $number) {
if ($number > 10) {
break;
}
echo $number, ' ';
}This prints 2 4 6 8 10.
Reading a large file
The main use is processing data that does not fit in memory: large files, database result sets. A generator reads a file line by line:
<?php
function readLines(string $path)
{
$handle = fopen($path, 'r');
while (($line = fgets($handle)) !== false) {
yield rtrim($line);
}
fclose($handle);
}
$file = sys_get_temp_dir() . '/lines-demo.txt';
file_put_contents($file, "first\nsecond\nthird\n");
foreach (readLines($file) as $number => $line) {
echo $number + 1, '. ', $line, "\n";
}Common mistake
A generator can be walked through only once. After the loop ends it cannot be restarted — the function has to be called again. count() and index access do not work on it either:
<?php
function letters()
{
yield 'a';
yield 'b';
}
$generator = letters();
echo implode(', ', iterator_to_array($generator)), "\n"; // a, b
echo count(iterator_to_array(letters())); // 2iterator_to_array() turns a generator into an array — but then the memory saving is lost.