BehavioralPHPverifiedVerified
Iterator Pattern in PHP
Provides a way to sequentially access elements of a collection without exposing its underlying representation.
How to Implement the Iterator Pattern in PHP
1Step 1: Define an iterable collection using PHP's built-in iterator interface
class NumberRange implements \Iterator
{
private int $current;
public function __construct(
private readonly int $start,
private readonly int $end,
) {
$this->current = $start;
}
public function current(): int { return $this->current; }
public function key(): int { return $this->current - $this->start; }
public function next(): void { $this->current++; }
public function rewind(): void { $this->current = $this->start; }
public function valid(): bool { return $this->current <= $this->end; }
}2Step 2: Use the iterator with foreach
$range = new NumberRange(1, 5);
foreach ($range as $index => $value) {
echo "Index {$index}: {$value}\n";
}3Step 3: Demonstrate a generator-based iterator
function fibonacci(int $limit): \Generator
{
$a = 0;
$b = 1;
for ($i = 0; $i < $limit; $i++) {
yield $a;
[$a, $b] = [$b, $a + $b];
}
}
foreach (fibonacci(10) as $num) {
echo "{$num} ";
}<?php
declare(strict_types=1);
// [step] Define a type-safe paginated iterator for database results
/** @template T */
interface PageFetcherInterface
{
/** @return T[] */
public function fetch(int $offset, int $limit): array;
public function count(): int;
}
/**
* Lazily iterates over paginated data without loading everything in memory.
* @template T
* @implements \Iterator<int, T>
*/
final class PaginatedIterator implements \Iterator, \Countable
{
private int $currentIndex = 0;
private int $pageIndex = 0;
/** @var T[] */
private array $currentPage = [];
private ?int $totalCount = null;
/**
* @param PageFetcherInterface<T> $fetcher
*/
public function __construct(
private readonly PageFetcherInterface $fetcher,
private readonly int $pageSize = 100,
) {
if ($pageSize <= 0) {
throw new \InvalidArgumentException("Page size must be positive, got {$pageSize}");
}
}
/** @return T */
public function current(): mixed
{
return $this->currentPage[$this->pageIndex];
}
public function key(): int
{
return $this->currentIndex;
}
public function next(): void
{
$this->currentIndex++;
$this->pageIndex++;
// Load next page if we've exhausted the current one
if ($this->pageIndex >= count($this->currentPage) && $this->valid()) {
$this->loadPage();
}
}
public function rewind(): void
{
$this->currentIndex = 0;
$this->pageIndex = 0;
$this->loadPage();
}
public function valid(): bool
{
return $this->currentIndex < $this->count();
}
public function count(): int
{
if ($this->totalCount === null) {
$this->totalCount = $this->fetcher->count();
}
return $this->totalCount;
}
private function loadPage(): void
{
$offset = $this->currentIndex;
$this->currentPage = $this->fetcher->fetch($offset, $this->pageSize);
$this->pageIndex = 0;
}
}
// [step] Implement chainable iterator operations using generators
final class IteratorPipeline
{
/** @param iterable<mixed> $source */
public function __construct(
private readonly iterable $source,
) {}
/** @return self */
public function filter(callable $predicate): self
{
return new self($this->filterGenerator($predicate));
}
/** @return self */
public function map(callable $transform): self
{
return new self($this->mapGenerator($transform));
}
public function take(int $count): self
{
return new self($this->takeGenerator($count));
}
/** @return array<mixed> */
public function toArray(): array
{
return iterator_to_array($this->source, false);
}
public function forEach(callable $callback): void
{
foreach ($this->source as $key => $value) {
$callback($value, $key);
}
}
private function filterGenerator(callable $predicate): \Generator
{
foreach ($this->source as $value) {
if ($predicate($value)) yield $value;
}
}
private function mapGenerator(callable $transform): \Generator
{
foreach ($this->source as $value) {
yield $transform($value);
}
}
private function takeGenerator(int $count): \Generator
{
$i = 0;
foreach ($this->source as $value) {
if ($i >= $count) break;
yield $value;
$i++;
}
}
}Iterator Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Iterator Pattern in the Real World
“Think of a TV remote control. Whether your playlist is on a Blu-ray disc, a streaming service, or a USB drive, you use the same next and previous buttons to cycle through content. The remote (iterator interface) abstracts away the completely different internal mechanisms each media source uses to retrieve the next item.”