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
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
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
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
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
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
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
finallyblock - 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
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.