Generators and iterators

PHP · Advanced · 14 min

Generators let you stream big files and query results without loading everything into memory.

A function containing yield becomes a generator: calling it returns a Generator object and runs nothing yet. Each foreach step resumes the function until the next yield, receives that value, and pauses again.

function lines(string $path): Generator {
    $h = fopen($path, 'r');
    try {
        while (($line = fgets($h)) !== false) yield rtrim($line, "\n");
    } finally { fclose($h); }
}

This reads a 10 GB file with constant memory, because only one line exists at a time. Generators compose: one generator can filter or map another, building lazy pipelines.

  • yield $key => $value yields keys too.
  • yield from $other delegates to another generator or array.
  • A generator can return a final value, read with $gen->getReturn() after it finishes.
  • $gen->send($x) resumes it and makes the paused yield expression evaluate to $x, enabling two-way coroutines.

A generator runs once; it can't be rewound after it has started. To make an object iterable many times, implement IteratorAggregate and return a fresh generator from getIterator().

Watch out with iterator_to_array($gen): it uses keys by default, and yield from restarts keys at 0 for each inner source, so values overwrite each other. Pass preserve_keys: false.

Example

<?php
function numbers(int $max): Generator {
    for ($i = 1; $i <= $max; $i++) {
        echo "[make $i]";
        yield $i;
    }
    return 'done';
}

function evens(iterable $xs): Generator {
    foreach ($xs as $x) if ($x % 2 === 0) yield $x;
}

$gen = numbers(4);
foreach (evens($gen) as $n) echo " got $n\n";
// [make 1][make 2] got 2
// [make 3][make 4] got 4
echo $gen->getReturn(), "\n";           // done

function both(): Generator { yield from [1, 2]; yield from [3]; }
print_r(iterator_to_array(both()));      // [0 => 3, 1 => 2]: keys collided!
print_r(iterator_to_array(both(), preserve_keys: false));  // [1, 2, 3]

Remember

  • Calling a generator function runs nothing until you iterate
  • Generators stream data in constant memory and compose into pipelines
  • Use preserve_keys: false with iterator_to_array after yield from

Watch out for

  • Trying to iterate the same generator twice; it throws once it has run.

Check yourself

  1. What does calling numbers(4) (a generator function) do immediately?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.php.net/manual/en/language.generators.php