PHP Generators

Generators let you produce sequences one value at a time without building giant arrays. Process gigabyte files in kilobytes of memory and compose lazy pipelines.

Advanced 8 min read 9 examples

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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
Generators implement Iterator

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.

Next Steps

Frequently Asked Questions

An array holds all values in memory at once. A generator produces values one at a time on demand. For a 10 million item sequence, an array might use 1GB while a generator uses kilobytes.

No - generators are one-shot iterators. Once consumed, you must call the generator function again to get a fresh instance. If you need rewind, materialise into an array with iterator_to_array() (you lose the memory benefit).

When (a) the source is huge and you don't need all of it loaded, (b) the caller might stop early (paginated views), or (c) you want to build a pipeline of transformations without intermediate arrays.

Yes - yield from $anotherGen delegates all values from another iterable. Useful for composing generators or flattening nested structures.

Marginally - generator setup has overhead. For 10 items, an array is faster. For 10,000+ items where memory matters, the generator wins decisively.