PHP Generators
last modified May 21, 2025
In this article, we explore PHP generators, a powerful feature that allows for efficient iteration over large datasets while minimizing memory usage.
Generators provide a lightweight alternative to traditional iterators by
yielding values one at a time, rather than storing entire collections in memory.
Introduced in PHP 5.5, they allow developers to use foreach
loops
on large data sources without needing to construct full arrays, making them
ideal for handling large files, database records, and API results efficiently.
The advantages of PHP generators are:
- Memory Efficiency: Avoids loading entire datasets into memory, improving performance.
- Lazy Evaluation: Computes values only when needed, reducing processing overhead.
- Simpler Iterator Implementation: No need to implement the
Iterator
interface manually. - Stream Processing: Ideal for iterating over large files or database queries.
- Improved Performance: Reduces execution time compared to traditional loops over large datasets.
By using generators, developers can optimize performance and memory usage when dealing with large datasets, making them an essential tool for efficient data handling in PHP.
Basic generator syntax
Generators are functions that use the yield keyword instead of return. When called, they return a Generator object that can be iterated over. Each yield provides one value to the iteration.
<?php declare(strict_types=1); function numberGenerator(int $start, int $end): Generator { for ($i = $start; $i <= $end; $i++) { yield $i; } } // Using the generator foreach (numberGenerator(1, 5) as $number) { echo "$number\n"; } // Generator returns a Generator object $generator = numberGenerator(1, 3); echo get_class($generator) . "\n"; // Generator // Manual iteration $generator = numberGenerator(10, 12); echo $generator->current() . "\n"; // 10 $generator->next(); echo $generator->current() . "\n"; // 11
This shows a basic generator that yields numbers in a range. The generator preserves its state between yields, allowing it to resume where it left off. Generators are interruptible functions that can be paused and resumed.
λ php basic_generator.php 1 2 3 4 5 Generator 10 11
Memory efficiency
Generators are memory-efficient because they only generate one value at a time, unlike arrays which must store all values in memory. This makes them ideal for working with large datasets.
<?php declare(strict_types=1); // Memory intensive approach function getLinesFromFile(string $filename): array { $lines = []; $file = fopen($filename, 'r'); while (!feof($file)) { $lines[] = trim(fgets($file)); } fclose($file); return $lines; } // Generator approach function getLinesFromFileGenerator(string $filename): Generator { $file = fopen($filename, 'r'); while (!feof($file)) { yield trim(fgets($file)); } fclose($file); } // Memory usage comparison $filename = 'large_file.txt'; // Array approach $memoryBefore = memory_get_usage(); $lines = getLinesFromFile($filename); $memoryAfter = memory_get_usage(); echo "Array memory: " . ($memoryAfter - $memoryBefore) . " bytes\n"; // Generator approach $memoryBefore = memory_get_usage(); $linesGenerator = getLinesFromFileGenerator($filename); $memoryAfter = memory_get_usage(); echo "Generator memory: " . ($memoryAfter - $memoryBefore) . " bytes\n"; // Process large file without loading it all foreach (getLinesFromFileGenerator($filename) as $line) { // Process each line without memory issues if (str_contains($line, 'error')) { echo "Found error in line: $line\n"; } }
The generator version uses significantly less memory because it only holds one line in memory at a time. This difference becomes crucial when working with files that are too large to fit in memory all at once.
λ php memory_efficiency.php Array memory: 10485760 bytes Generator memory: 416 bytes Found error in line: Sample error line 1 Found error in line: Sample error line 2
Keyed yields
Generators can yield key-value pairs, making them suitable for generating
associative sequences. This is done by yielding an array or using the
yield
key =>
value syntax.
<?php declare(strict_types=1); function assocGenerator(): Generator { yield 'name' => 'Alice'; yield 'age' => 30; yield 'occupation' => 'Developer'; } foreach (assocGenerator() as $key => $value) { echo "$key: $value\n"; } // More complex example function csvGenerator(string $filename): Generator { $file = fopen($filename, 'r'); // Get headers $headers = fgetcsv($file); while ($row = fgetcsv($file)) { yield array_combine($headers, $row); } fclose($file); } // Process CSV file foreach (csvGenerator('data.csv') as $record) { echo "Processing: {$record['name']} ({$record['email']})\n"; } // Numeric keys function indexedGenerator(): Generator { for ($i = 0; $i < 5; $i++) { yield $i => "Item $i"; } } foreach (indexedGenerator() as $index => $item) { echo "$index: $item\n"; }
This demonstrates how generators can produce associative arrays and handle structured data like CSV files. The key-value pairs are yielded directly and can be used in foreach loops just like regular associative arrays.
λ php keyed_yields.php name: Alice age: 30 occupation: Developer Processing: John Doe (john@example.com) Processing: Jane Smith (jane@example.com) 0: Item 0 1: Item 1 2: Item 2 3: Item 3 4: Item 4
Generator delegation
PHP 7.0 introduced generator delegation using yield from
, which
allows a generator to yield values from another generator, array
,
or
Traversable
object. This enables generator composition and cleaner
code.
<?php declare(strict_types=1); function countTo3(): Generator { yield 1; yield 2; yield 3; } function countTo6(): Generator { yield from countTo3(); yield 4; yield 5; yield 6; } foreach (countTo6() as $number) { echo "$number "; } echo "\n"; // Yield from array function yieldArray(): Generator { yield from ['a', 'b', 'c']; } foreach (yieldArray() as $letter) { echo "$letter "; } echo "\n"; // Complex delegation function readMultipleFiles(array $filenames): Generator { foreach ($filenames as $filename) { yield from readFileLines($filename); } } function readFileLines(string $filename): Generator { $file = fopen($filename, 'r'); while (!feof($file)) { yield trim(fgets($file)); } fclose($file); } // Process multiple files as one sequence foreach (readMultipleFiles(['file1.txt', 'file2.txt']) as $line) { echo "Line: $line\n"; } // Yield from with keys function combinedGenerator(): Generator { yield from ['a' => 1, 'b' => 2]; yield from ['c' => 3]; yield 'd' => 4; } foreach (combinedGenerator() as $k => $v) { echo "$k:$v "; } echo "\n";
Generator delegation simplifies working with multiple generators by allowing them to be combined seamlessly. The yield from syntax handles all the iteration internally, making the code cleaner and more modular.
λ php generator_delegation.php 1 2 3 4 5 6 a b c Line: First line of file1 Line: Second line of file1 Line: First line of file2 a:1 b:2 c:3 d:4
Generator methods
Generator objects provide several methods for more advanced control over
iteration. These include send
, throw
, and
getReturn
for two-way communication with the generator.
<?php declare(strict_types=1); function interactiveGenerator(): Generator { $value = yield 'first'; echo "Received: $value\n"; $value = yield 'second'; echo "Received: $value\n"; return 'done'; } $gen = interactiveGenerator(); // Get first yielded value echo "Yielded: " . $gen->current() . "\n"; // Send value to generator $gen->send('hello'); // Get next yielded value echo "Yielded: " . $gen->current() . "\n"; // Send another value $gen->send('world'); // Get return value try { $gen->next(); echo "Returned: " . $gen->getReturn() . "\n"; } catch (Exception $e) { echo "Exception: " . $e->getMessage() . "\n"; } // Exception handling in generators function failingGenerator(): Generator { try { yield 'start'; throw new Exception('Generator error'); } catch (Exception $e) { yield 'caught: ' . $e->getMessage(); } yield 'end'; } $gen = failingGenerator(); echo $gen->current() . "\n"; $gen->next(); echo $gen->current() . "\n"; $gen->next(); echo $gen->current() . "\n";
This demonstrates two-way communication with generators. Values can be sent into the generator using send(), and exceptions can be thrown into it with throw(). The return value is accessed via getReturn() after iteration completes.
λ php generator_methods.php Yielded: first Received: hello Yielded: second Received: world Returned: done start caught: Generator error end
Real-world use cases
Generators are useful in many practical scenarios where memory efficiency or lazy evaluation is important. Here are some common real-world examples.
<?php declare(strict_types=1); // 1. Processing large files function processLargeFile(string $filename): Generator { $file = fopen($filename, 'r'); while (!feof($file)) { $line = fgets($file); yield json_decode($line, true); } fclose($file); } // 2. Paginating database results function paginateResults(PDO $pdo, string $query, int $pageSize = 100): Generator { $offset = 0; do { $stmt = $pdo->prepare($query . " LIMIT ? OFFSET ?"); $stmt->execute([$pageSize, $offset]); $results = $stmt->fetchAll(); yield from $results; $offset += $pageSize; } while (count($results) === $pageSize); } // 3. Generating infinite sequences function fibonacciSequence(): Generator { $a = 0; $b = 1; while (true) { yield $a; [$a, $b] = [$b, $a + $b]; } } $fib = fibonacciSequence(); for ($i = 0; $i < 10; $i++) { echo $fib->current() . " "; $fib->next(); } echo "\n"; // 4. Processing API responses function fetchPaginatedAPI(string $baseUrl): Generator { $url = $baseUrl; do { $response = json_decode(file_get_contents($url), true); yield from $response['data']; $url = $response['next_page'] ?? null; } while ($url); } // 5. Data transformation pipeline function transformData(iterable $source, callable ...$transformers): Generator { foreach ($source as $item) { $result = $item; foreach ($transformers as $transformer) { $result = $transformer($result); } yield $result; } } // Example pipeline $data = [' apple ', ' banana ', ' cherry ']; $pipeline = transformData( $data, fn($s) => trim($s), fn($s) => strtoupper($s), fn($s) => "FRUIT: $s" ); foreach ($pipeline as $item) { echo "$item\n"; }
These examples show generators solving real problems elegantly. From handling large datasets to creating processing pipelines, generators provide memory- efficient solutions that would be difficult with traditional arrays.
λ php real_world.php 0 1 1 2 3 5 8 13 21 34 FRUIT: APPLE FRUIT: BANANA FRUIT: CHERRY
Best practices
Follow these best practices when working with generators to write clean, efficient, and maintainable code.
For small datasets, performance considerations are essential. In many cases, arrays offer better efficiency than generators, as they allow direct indexing and faster iteration without the overhead of maintaining generator state.
// Bad: function getAllUsers(): array { $users = []; // Fetch all from DB return $users; } // Good: function getAllUsersGenerator(): Generator { // Fetch one at a time while ($user = fetchUserFromDB()) { yield $user; } }
Use generators for memory efficiency with large datasets
/** @return Generator<int, User, mixed, void> */ function getUserGenerator(): Generator { // ... }
Document generator return types.
function combinedData(): Generator { yield from getUsers(); yield from getProducts(); }
Use yield from
for generator composition.
function readFileWithCleanup(string $filename): Generator { $file = fopen($filename, 'r'); try { while (!feof($file)) { yield trim(fgets($file)); } } finally { fclose($file); } }
Clean up resources in generators.
$gen = someGenerator(); foreach ($gen as $value) { // ... } // Can't iterate again - generator is exhausted // foreach ($gen as $value) {} // Won't work
Be aware that generators are forward-only. Once a generator has been exhausted, it cannot be reset or reused. If you need to iterate over the same values multiple times, consider storing them in an array or using a different generator.
function filter(iterable $items, callable $predicate): Generator { foreach ($items as $item) { if ($predicate($item)) { yield $item; } } }
Use generators for lazy evaluation.
// Works well with match, arrow functions, etc. function transformGenerator(iterable $items): Generator { foreach ($items as $item) { yield match(true) { is_int($item) => $item * 2, is_string($item) => strtoupper($item), default => $item }; } }
Use generators with other language features like match and arrow functions for cleaner code..
/** * @yield int The generated ID * @yield string The associated name */ function idNameGenerator(): Generator { // ... }
Document expected yields for better code clarity.
These practices help ensure generators are used effectively. Key points include proper documentation, resource cleanup, and choosing generators for memory efficiency rather than convenience in all cases.
PHP generators provide a powerful tool for efficient iteration and working with large datasets. Key points to remember:
- Generators use
yield
to produce values one at a time. - Memory-efficient alternative to building full arrays.
- Maintain state between
yield
operations. - Can yield key-value pairs for associative iteration.
- Support delegation with
yield from
(PHP 7.0+). - Enable two-way communication via
send
andthrow
. - Return values available after iteration completes (PHP 7.0+).
- Forward-only iteration (cannot rewind once progressed).
- Ideal for large datasets and lazy evaluation techniques.
Generators are a versatile feature that can significantly improve memory usage and code clarity when working with sequences of data. They're particularly valuable in data processing pipelines and when dealing with large or infinite sequences.
Author
List all PHP tutorials.