advanced php language

Generators

Generators let a function produce values one at a time with yield instead of building and returning a full array. They are useful for lazy iteration, large datasets, file processing, streams, and pipelines.

A generator function looks like a normal function, but when it contains yield, calling it returns a Generator object. The function body runs as the generator is iterated.

Basic yield

PHP example
<?php

declare(strict_types=1);

function numbers(): Generator
{
    yield 1;
    yield 2;
    yield 3;
}

foreach (numbers() as $number) {
    echo $number . PHP_EOL;
}

// Prints:
// 1
// 2
// 3

The values are produced one at a time. PHP does not need to build an array [1, 2, 3] first.

Generators Are Lazy

Generator code runs only when iteration asks for the next value.

PHP example
<?php

declare(strict_types=1);

function lazyNumbers(): Generator
{
    echo 'start' . PHP_EOL;

    yield 1;
    yield 2;
}

$numbers = lazyNumbers();

echo 'before loop' . PHP_EOL;

foreach ($numbers as $number) {
    echo $number . PHP_EOL;
}

// Prints:
// before loop
// start
// 1
// 2

Creating the generator did not run the function body. Iterating did.

Processing Large Files

Generators are useful when a file is too large to load into memory all at once.

PHP example
<?php

declare(strict_types=1);

function linesFromFile(string $path): Generator
{
    $handle = fopen($path, 'rb');

    if ($handle === false) {
        throw new RuntimeException('Could not open file.');
    }

    try {
        while (($line = fgets($handle)) !== false) {
            yield rtrim($line, "\r\n");
        }
    } finally {
        fclose($handle);
    }
}

$path = tempnam(sys_get_temp_dir(), 'lines_');
file_put_contents($path, "first\nsecond\n");

foreach (linesFromFile($path) as $line) {
    echo $line . PHP_EOL;
}

unlink($path);

// Prints:
// first
// second

The finally block closes the file even if iteration stops early or an exception happens.

Keys

Generators can yield keys as well as values.

PHP example
<?php

declare(strict_types=1);

function statusLabels(): Generator
{
    yield 'draft' => 'Draft';
    yield 'paid' => 'Payment received';
    yield 'shipped' => 'On the way';
}

foreach (statusLabels() as $value => $label) {
    echo $value . ': ' . $label . PHP_EOL;
}

// Prints:
// draft: Draft
// paid: Payment received
// shipped: On the way

This is useful when lazily building options or mappings.

yield from

yield from delegates to another iterable.

PHP example
<?php

declare(strict_types=1);

function standardFormats(): Generator
{
    yield 'csv';
    yield 'json';
}

function allFormats(): Generator
{
    yield from standardFormats();
    yield 'xml';
}

foreach (allFormats() as $format) {
    echo $format . PHP_EOL;
}

// Prints:
// csv
// json
// xml

This helps compose generators without manually looping and yielding each value.

Generator Return Values

A generator can return a final value, but normal foreach does not expose it. You can access it through the generator object after iteration.

PHP example
<?php

declare(strict_types=1);

function importRows(): Generator
{
    yield 'row one';
    yield 'row two';

    return 2;
}

$rows = importRows();

foreach ($rows as $row) {
    echo $row . PHP_EOL;
}

echo 'Imported ' . $rows->getReturn() . ' rows.' . PHP_EOL;

// Prints:
// row one
// row two
// Imported 2 rows.

Most everyday generator code does not need return values, but they are useful in some pipelines.

When Arrays Are Clearer

Do not use generators for every list. Arrays are simpler when the dataset is small, already in memory, and needs random access, sorting, counting, or reuse.

Generators are best when:

  • values are expensive to build
  • the source may be large
  • values are processed sequentially
  • you want to compose lazy pipelines
  • memory usage matters

Remember that generators are usually one-pass. Once consumed, they cannot be rewound like a normal array.

What You Should Be Able To Do

After this lesson, you should be able to write generator functions with yield, use keys, use yield from, process a file lazily, and decide whether a generator or array is more appropriate.

For junior work, this matters because generators are common in import/export code, data pipelines, framework internals, and memory-sensitive jobs.

Practice

Practice: Read Lines Lazily

Create a small PHP example that reads lines from a file with a generator.

Task

Build a linesFromFile() generator that:

  • opens a file
  • yields each line without its newline
  • closes the file in a finally block
  • throws a clear exception if the file cannot be opened

Use strict types. Keep the expected output in the PHP code block as printed lines or comments.

Check Your Work

Run cases for:

  • reading a file with two lines
  • trying to read a missing file

Afterward, explain why this is more memory-friendly than loading the whole file into an array.

Show solution

This solution yields one line at a time and closes the file handle reliably.

PHP example
<?php

declare(strict_types=1);

function linesFromFile(string $path): Generator
{
    $handle = fopen($path, 'rb');

    if ($handle === false) {
        throw new RuntimeException('Could not open file.');
    }

    try {
        while (($line = fgets($handle)) !== false) {
            yield rtrim($line, "\r\n");
        }
    } finally {
        fclose($handle);
    }
}

$path = tempnam(sys_get_temp_dir(), 'lines_');
file_put_contents($path, "first\nsecond\n");

foreach (linesFromFile($path) as $line) {
    echo $line . PHP_EOL;
}

unlink($path);

try {
    foreach (linesFromFile('/path/that/does/not/exist') as $line) {
        echo $line . PHP_EOL;
    }
} catch (RuntimeException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// first
// second
// Could not open file.

This is more memory-friendly than loading the whole file into an array because only the current line needs to be held while the loop runs.