Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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; // 500000500000

An 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()));                 // 2

iterator_to_array() turns a generator into an array — but then the memory saving is lost.

Thanks for reading!

Donate to the site — we work hard for you.