← Generators

Ashterix

docs: English JsonRpcBundle docs, REST and MCP Bundle sections, package cards

en/articles/from-tg/series/php-can-do/generators.md new file +129 −0

Generators

An old tool most people still don't use

  • Added in: PHP 5.5
  • What it gives you: functions with yield that return elements one at a time — instead of collecting everything in memory and handing it over at once.

The reality

Even though generators have existed in PHP since 5.5, at interviews most developers either can't explain where exactly they save memory, or mention them only in theory — something like "it's a thing for big data". As for practical cases — zero ideas.

So let me throw you a few ideas on how and where they can be used.

How they actually work

Unlike regular iterators or arrays:

  • a generator doesn't know how many elements there will be;
  • it doesn't know whether there's a next one;
  • it doesn't remember whether there was a previous iteration;
  • it's single-pass — once you've gone through it, that's it, you can't go back.

And despite all these "limitations" it has one major advantage: it can pause its own execution, which means you don't keep all the states in memory, only the current one.

It's not an "array" but rather a stream — and that's exactly why it's priceless in the right place.

Real cases where yield is truly needed

1. Reading from the database page by page

function fetchUsers(PDO $db, int $limit = 500): Generator 
{
    $offset = 0;
    while (true) {
        $rows = $db->query("SELECT * FROM users LIMIT $limit OFFSET $offset")->fetchAll();
        if (!$rows) break;
        yield from $rows;
        $offset += $limit;
    }
}

The memory savings are enormous — it works even on tables with hundreds of thousands of rows.

2. Streaming parsing of large files

function readCsv(string $path): Generator 
{
    $h = fopen($path, 'r');
    while (($row = fgetcsv($h)) !== false) {
        yield $row;
    }
    fclose($h);
}

It reads line by line — the whole CSV never gets loaded into memory.

3. Streaming the result into a file

function streamCsvRows(iterable $data): Generator 
{
    foreach ($data as $row) {
        yield implode(',', $row) . "\n";
    }
}

header('Content-Type: text/csv');
foreach (streamCsvRows($repository->getLargeDataset()) as $line) {
    echo $line;
    flush();
}

Sending large reports or exports — without buffering, in real time.

4. Loading from an API gradually

function fetchPages(ApiClient $client): Generator 
{
    $page = 1;
    do {
        $data = $client->getPage($page++);
        yield from $data['items'];
    } while ($data['has_more']);
}

Perfect for streaming data processing without buffering.

5. Walking a large file tree

function scanDirRecursive(string $path): Generator 
{
    foreach (scandir($path) as $item) {
        if ($item === '.' || $item === '..') continue;
        $full = "$path/$item";
        if (is_dir($full)) {
            yield from scanDirRecursive($full);
        } else {
            yield $full;
        }
    }
}

foreach (scanDirRecursive('/var/log') as $file) {
    echo $file . PHP_EOL;
}

Perfect for walking directories, even if there are thousands of them.

Conclusion

Generators are a tool not for convenience, but for efficiency. They don't let you rewind or find out the size, but that's exactly why they can handle unbounded streams of data.

If you've ever written a script that crashed with out of memory, yield is your new friend.


First published in Ukrainian in the Telegram channel “Created to die” on October 18, 2025.