ZetCode

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:

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.

basic_iterator.php
<?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.

builtin_iterators.php
<?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.

iterator_aggregate.php
<?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.

best_practices.php
<?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:

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all PHP tutorials.