PHP Iterator Design Pattern Поведенческий паттерн

Паттерн Итератор (Iterator) в PHP

Короткий скринкаст: паттерн «Итератор» (Iterator) — даём единый способ обхода коллекции, не раскрывая её внутреннее устройство. Обходим полку книг через foreach.

Iterator Pattern — открыть страницу урока

Описание

Итератор предоставляет последовательный доступ к элементам коллекции, скрывая её внутреннее представление. Сама полка BookShelf умеет только добавлять книги и реализует IteratorAggregate, возвращая свой итератор из getIterator(). Итератор BookIterator хранит курсор position и реализует методы rewind, valid, current, next и key. Клиент просто пишет foreach ($shelf as $book) и не знает, как перебирается коллекция внутри — PHP сам вызывает методы итератора.

UML-диаграмма

classDiagram class Iterator { <<interface>> +current(): Book +key(): int +next(): void +rewind(): void +valid(): bool } class IteratorAggregate { <<interface>> +getIterator(): Iterator } class Book { +title: string } class BookIterator { -books: array -position: int +current(): Book +key(): int +next(): void +rewind(): void +valid(): bool } class BookShelf { -books: array +add(book: Book): void +getIterator(): Iterator } Iterator <|-- BookIterator : implements IteratorAggregate <|-- BookShelf : implements BookShelf ..> BookIterator : creates BookIterator o-- Book : iterates

Код

Book.php

class Book
{
    public function __construct(public string $title) {}
}

BookIterator.php

class BookIterator implements Iterator
{
    private int $position = 0;

    public function __construct(private array $books) {}

    public function current(): Book
    {
        return $this->books[$this->position];
    }

    public function key(): int
    {
        return $this->position;
    }

    public function next(): void
    {
        $this->position++;
    }

    public function rewind(): void
    {
        $this->position = 0;
    }

    public function valid(): bool
    {
        return isset($this->books[$this->position]);
    }
}

BookShelf.php

class BookShelf implements IteratorAggregate
{
    private array $books = [];

    public function add(Book $book): void
    {
        $this->books[] = $book;
    }

    public function getIterator(): Iterator
    {
        return new BookIterator($this->books);
    }
}

demo.php

require_once 'Book.php';
require_once 'BookIterator.php';
require_once 'BookShelf.php';

$shelf = new BookShelf;
$shelf->add(new Book('Refactoring'));
$shelf->add(new Book('Clean Code'));
$shelf->add(new Book('Design Patterns'));

foreach ($shelf as $book) {
    echo $book->title.PHP_EOL;
}

Пример результата

После php demo.php полка обходится как обычный массив, хотя внутри работает собственный итератор:

Refactoring
Clean Code
Design Patterns

Преимущества

  • Клиент обходит коллекцию единообразно и не знает её внутреннего устройства.
  • Логика обхода отделена от самой коллекции.
  • Можно иметь несколько разных итераторов для одной коллекции (по порядку, фильтру, в обратную сторону).
  • foreach работает с любой коллекцией, реализующей Iterator или IteratorAggregate.

Когда использовать

  • Когда нужно обходить коллекцию, не раскрывая её внутреннюю структуру.
  • Когда для одной коллекции нужны разные способы обхода.
  • Когда требуется единый интерфейс для обхода разных структур данных.
  • Когда надо поддержать несколько одновременных обходов одной коллекции.

Использование в Laravel

В Laravel Итератор повсюду. Коллекции Illuminate\Support\Collection и Illuminate\Database\Eloquent\Collection реализуют IteratorAggregate, поэтому обходятся в foreach. Построитель запросов отдаёт ленивый Illuminate\Support\LazyCollection, а Illuminate\Database\Eloquent\Cursor позволяет обойти миллионы строк без загрузки всех в память. Файловая система Illuminate\Filesystem\Filesystem возвращает Symfony\Component\Finder\Finder, который тоже является итератором по файлам.