What is a Generator?
Any function containing the yield keyword is a generator. Calling it returns a Generator object instead of running the body. Values are produced lazily as the caller iterates.
Your First Generator
<?php
function counter(int $start, int $end): Generator {
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}
foreach (counter(1, 5) as $n) {
echo $n . " "; // 1 2 3 4 5
}
// vs the array equivalent which builds [1,2,3,4,5] in memory:
foreach (range(1, 5) as $n) { /* ... */ }
// For 10 million items, the generator stays under 1KB.
// The array uses hundreds of MB.
Keyed yield
<?php
function envVars(): Generator {
yield "host" => "localhost";
yield "port" => 3306;
yield "debug" => true;
}
foreach (envVars() as $key => $value) {
echo "$key = $value\n";
}
yield from
Delegate to another iterable - perfect for composing generators or flattening:
<?php
function letters(): Generator {
yield "a";
yield "b";
yield from ["c", "d", "e"]; // delegate to array
yield from numbers(); // delegate to another generator
yield "z";
}
function numbers(): Generator {
yield 1; yield 2; yield 3;
}
foreach (letters() as $v) echo $v . " ";
// a b c d e 1 2 3 z
send() - Two-way Communication
Generators are coroutines - the caller can send values back into the generator:
<?php
function logger(): Generator {
while (true) {
$msg = yield; // pause and wait
if ($msg === null) return;
echo "[" . date("H:i:s") . "] $msg\n";
}
}
$g = logger();
$g->current(); // prime the generator
$g->send("Started");
$g->send("Processing");
$g->send("Finished");
Memory-Efficient I/O
The killer use case - process huge files line by line without loading them whole:
<?php
function readLines(string $path): Generator {
$fp = fopen($path, "r");
try {
while (($line = fgets($fp)) !== false) {
yield rtrim($line, "\n");
}
} finally {
fclose($fp);
}
}
// Process a 10GB log file in O(1) memory
foreach (readLines("server.log") as $line) {
if (str_contains($line, "ERROR")) {
echo $line . "\n";
}
}
// Pull database rows lazily
function fetchAll(PDOStatement $stmt): Generator {
while ($row = $stmt->fetch()) {
yield $row;
}
}
$stmt = $pdo->query("SELECT * FROM huge_table");
foreach (fetchAll($stmt) as $row) {
process($row);
}
Generator Pipelines
<?php
function map(iterable $src, callable $fn): Generator {
foreach ($src as $k => $v) yield $k => $fn($v);
}
function filter(iterable $src, callable $pred): Generator {
foreach ($src as $k => $v) {
if ($pred($v)) yield $k => $v;
}
}
function take(iterable $src, int $n): Generator {
$i = 0;
foreach ($src as $k => $v) {
if ($i++ >= $n) return;
yield $k => $v;
}
}
// Compose - all lazy, no intermediate arrays
$result = take(
map(
filter(
readLines("data.txt"),
fn($line) => str_contains($line, "ERROR")
),
fn($line) => strtoupper($line)
),
100
);
foreach ($result as $line) echo $line . "\n";
return Values
A generator can return a final summary value with return, retrieved via getReturn() after the iteration:
<?php
function countingYielder(array $items): Generator {
foreach ($items as $i) yield $i;
return count($items);
}
$g = countingYielder([10, 20, 30]);
foreach ($g as $v) echo $v . " ";
echo "\nTotal yielded: " . $g->getReturn(); // 3
Any function expecting Iterator or Traversable accepts a generator. They mesh with built-in functions like iterator_to_array(), iterator_count(), and class type hints accepting iterables.