PHP Iterators Tutorial
last modified May 21, 2025
In this article, we explore PHP iterators, a powerful feature that allows for custom iteration patterns over any data structure. Iterators provide a standardized way to traverse different data sources while maintaining clean separation between the iteration logic and the data itself.
PHP's Iterator interface enables objects to be iterated using foreach loops, giving developers fine-grained control over the iteration process. Unlike arrays which have fixed iteration behavior, custom iterators can implement complex traversal logic, filtering, or on-demand data loading.
The advantages of PHP iterators are:
- Custom Traversal Logic: Implement any iteration pattern for your data structures.
- Memory Efficiency: Process large datasets without loading everything into memory.
- Consistent Interface: Work with different data sources using the same foreach syntax.
- Lazy Evaluation: Load or compute data only when needed during iteration.
- Composability: Combine multiple iterators for powerful data processing pipelines.
By using iterators, developers can create flexible, reusable iteration patterns that work seamlessly with PHP's foreach construct while maintaining clean separation of concerns.
The Iterator interface
At the core of PHP's iteration system is the Iterator interface, which requires
implementing five methods: current
, key
,
next
, rewind
, and valid
.
These methods provide complete control over the iteration process.
<?php declare(strict_types=1); class NumberRangeIterator implements Iterator { private int $start; private int $end; private int $current; private int $key = 0; public function __construct(int $start, int $end) { $this->start = $start; $this->end = $end; } public function rewind(): void { $this->current = $this->start; $this->key = 0; } public function valid(): bool { return $this->current <= $this->end; } public function key(): int { return $this->key; } public function current(): int { return $this->current; } public function next(): void { $this->current++; $this->key++; } } // Using the iterator $iterator = new NumberRangeIterator(5, 10); foreach ($iterator as $key => $value) { echo "$key: $value\n"; } // Manual iteration $iterator->rewind(); while ($iterator->valid()) { echo $iterator->current() . "\n"; $iterator->next(); }
This basic iterator implementation demonstrates the core Iterator interface methods. The class generates numbers in a specified range, maintaining its own iteration state. Both foreach and manual iteration produce the same results, showing how foreach interacts with the iterator methods.
λ php basic_iterator.php 0: 5 1: 6 2: 7 3: 8 4: 9 5: 10 5 6 7 8 9 10
Built-in iterators
PHP provides several useful built-in iterators in the Standard PHP Library (SPL)
that solve common iteration problems. These include ArrayIterator
,
FilterIterator
, LimitIterator
, and more.
<?php declare(strict_types=1); // ArrayIterator - iterate over arrays with object interface $array = ['apple', 'banana', 'cherry']; $arrayIterator = new ArrayIterator($array); foreach ($arrayIterator as $item) { echo "$item\n"; } // FilterIterator - abstract class for custom filtering class OddNumberFilter extends FilterIterator { public function accept(): bool { return $this->current() % 2 !== 0; } } $numbers = new ArrayIterator(range(1, 10)); $oddFilter = new OddNumberFilter($numbers); echo "Odd numbers: " . implode(', ', iterator_to_array($oddFilter)) . "\n"; // LimitIterator - iterate over a subset of items $limitIterator = new LimitIterator( new ArrayIterator(range('a', 'z')), 5, // offset 10 // count ); echo "Letters 5-14: " . implode(', ', iterator_to_array($limitIterator)) . "\n"; // MultipleIterator - iterate over multiple iterators simultaneously $names = new ArrayIterator(['Alice', 'Bob', 'Charlie']); $ages = new ArrayIterator([25, 30, 35]); $multiIterator = new MultipleIterator(MultipleIterator::MIT_KEYS_ASSOC); $multiIterator->attachIterator($names, 'name'); $multiIterator->attachIterator($ages, 'age'); foreach ($multiIterator as $person) { echo "{$person['name']} is {$person['age']} years old\n"; } // RecursiveArrayIterator - for traversing nested structures $tree = [ 'fruit' => ['apple', 'banana'], 'vegetables' => ['carrot', 'pea'] ]; $recursiveIterator = new RecursiveIteratorIterator( new RecursiveArrayIterator($tree) ); echo "All items: " . implode(', ', iterator_to_array($recursiveIterator, false)) . "\n";
This example showcases several powerful SPL iterators. The FilterIterator
demonstrates custom filtering, while MultipleIterator
shows how to
combine parallel iterations. These built-in tools can often save you from
writing custom iterator implementations.
λ php builtin_iterators.php apple banana cherry Odd numbers: 1, 3, 5, 7, 9 Letters 5-14: f, g, h, i, j, k, l, m, n, o Alice is 25 years old Bob is 30 years old Charlie is 35 years old All items: apple, banana, carrot, pea
IteratorAggregate interface
The IteratorAggregate
interface provides a simpler way to make
objects iterable by delegating the iteration logic to a separate iterator. It
requires implementing just one method: getIterator
.
<?php declare(strict_types=1); class ProductCollection implements IteratorAggregate { private array $products; public function __construct(array $products) { $this->products = $products; } public function getIterator(): Traversable { return new ArrayIterator($this->products); } public function filter(callable $callback): self { return new self(array_filter($this->products, $callback)); } } // Using the IteratorAggregate $products = new ProductCollection([ ['id' => 1, 'name' => 'Laptop', 'price' => 999.99], ['id' => 2, 'name' => 'Phone', 'price' => 599.99], ['id' => 3, 'name' => 'Tablet', 'price' => 399.99] ]); echo "All products:\n"; foreach ($products as $product) { echo "- {$product['name']}: \${$product['price']}\n"; } // Chaining with filtered results echo "\nExpensive products:\n"; $expensive = $products->filter(fn($p) => $p['price'] > 500); foreach ($expensive as $product) { echo "- {$product['name']}: \${$product['price']}\n"; } // Custom iterator with IteratorAggregate class PaginatedResults implements IteratorAggregate { private int $pageSize; private int $currentPage = 0; private array $data; public function __construct(array $data, int $pageSize = 2) { $this->data = $data; $this->pageSize = $pageSize; } public function getIterator(): Traversable { $offset = $this->currentPage * $this->pageSize; return new ArrayIterator( array_slice($this->data, $offset, $this->pageSize) ); } public function nextPage(): void { $this->currentPage++; } public function hasMore(): bool { return ($this->currentPage + 1) * $this->pageSize < count($this->data); } } $paginated = new PaginatedResults(range(1, 5)); echo "\nPaginated results:\n"; do { foreach ($paginated as $item) { echo "$item "; } echo "\n"; $paginated->nextPage(); } while ($paginated->hasMore());
The IteratorAggregate
interface simplifies making classes iterable by
delegating to another iterator. This example shows both simple delegation
to ArrayIterator
and a more complex pagination implementation.
The interface is particularly useful when you want to maintain separation
between your collection class and its iteration logic.
λ php iterator_aggregate.php All products: - Laptop: $999.99 - Phone: $599.99 - Tablet: $399.99 Expensive products: - Laptop: $999.99 - Phone: $599.99 Paginated results: 1 2 3 4
Best practices
Follow these best practices when working with PHP iterators to create robust, maintainable, and efficient iteration code.
<?php declare(strict_types=1); // 1. Prefer IteratorAggregate for simple cases class SimpleCollection implements IteratorAggregate { private array $items; public function __construct(array $items) { $this->items = $items; } public function getIterator(): Traversable { return new ArrayIterator($this->items); } } // 2. Use existing SPL iterators when possible // Instead of writing custom iterators, check if SPL provides one $filtered = new CallbackFilterIterator( new ArrayIterator([1, 2, 3, 4, 5]), fn($n) => $n % 2 === 0 ); // 3. Document iterator behavior /** * @implements Iterator<int, User> */ class UserIterator implements Iterator { // ... } // 4. Handle resource cleanup class FileLineIterator implements Iterator { private $file; private $current; private int $key = 0; public function __construct(string $filename) { $this->file = fopen($filename, 'r'); if (!$this->file) { throw new RuntimeException("Cannot open file"); } } public function __destruct() { if (is_resource($this->file)) { fclose($this->file); } } // ... implement Iterator methods } // 5. Consider immutability // Iterators should generally not modify the underlying data during iteration // 6. Use iterator_to_array() cautiously // Converting large iterators to arrays loses memory benefits // 7. Implement SeekableIterator for random access class SeekableDataIterator implements SeekableIterator { private array $data; private int $position = 0; public function __construct(array $data) { $this->data = $data; } public function seek($position): void { if (!isset($this->data[$position])) { throw new OutOfBoundsException("Invalid position"); } $this->position = $position; } // ... implement other Iterator methods } // 8. Type hint for Traversable when appropriate function processItems(Traversable $items): void { foreach ($items as $item) { // Process item } } // 9. Combine iterators for complex processing $pipeline = new TransformIterator( new FilterIterator( new ArrayIterator($data), fn($x) => $x > 0 ), fn($x) => $x * 2 ); // 10. Consider performance for small datasets // For small fixed datasets, simple arrays may be more efficient
These practices help ensure your iterator implementations are correct, efficient, and maintainable. Key points include leveraging existing SPL iterators, proper resource management, and clear documentation of iterator behavior and types.
PHP iterators provide a powerful abstraction for custom iteration patterns. Key points to remember:
- Standardized Interface: The Iterator interface enables consistent iteration across different data structures.
- Memory Efficiency: Iterators can process large datasets without loading everything into memory.
- Flexibility: Implement any traversal logic needed for your specific data.
- SPL Utilities: PHP provides many built-in iterators for common patterns.
- Separation of Concerns: Keep iteration logic separate from data storage.
- Composability: Combine iterators to create powerful data processing pipelines.
- Lazy Evaluation: Process items only when needed during iteration.
Iterators are particularly valuable when working with large datasets, complex data structures, or when you need specialized traversal behavior. They form the foundation of PHP's object iteration capabilities and enable clean, efficient data processing patterns.
Author
List all PHP tutorials.