Паттерн Итератор (Iterator) в PHP
Короткий скринкаст: паттерн «Итератор» (Iterator) — даём единый способ обхода коллекции, не раскрывая её внутреннее устройство. Обходим полку книг через foreach.
Описание
Итератор предоставляет последовательный доступ к элементам коллекции, скрывая её внутреннее представление. Сама полка BookShelf умеет только добавлять книги и реализует IteratorAggregate, возвращая свой итератор из getIterator(). Итератор BookIterator хранит курсор position и реализует методы rewind, valid, current, next и key. Клиент просто пишет foreach ($shelf as $book) и не знает, как перебирается коллекция внутри — PHP сам вызывает методы итератора.
UML-диаграмма
Код
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, который тоже является итератором по файлам.