Применимость: Паттерн можно часто встретить в PHP-коде, особенно в программах, работающих с разными типами коллекций, и где требуется обход разных сущностей.
PHP имеет встроенный интерфейс для поддержки итераторов (Iterator), на основании которого можно строить свои Итераторы, совместимые с остальным PHP-кодом.
Признаки применения паттерна: Итератор легко определить по методам навигации (например, получения следующего/предыдущего элемента и т. д.). Код использующий итератор зачастую вообще не имеет ссылок на коллекцию, с которой работает итератор. Итератор либо принимает коллекцию в параметрах конструктора при создании, либо возвращается самой коллекцией.
Концептуальный пример
Этот пример показывает структуру паттерна Итератор, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
index.php: Пример структуры паттерна
<?php namespace RefactoringGuru\Iterator\Conceptual; /** * Конкретные Итераторы реализуют различные алгоритмы обхода. Эти классы * постоянно хранят текущее положение обхода. */ class AlphabeticalOrderIterator implements \Iterator { /** * @var WordsCollection */ private $collection; /** * @var int Хранит текущее положение обхода. У итератора может быть * множество других полей для хранения состояния итерации, особенно когда он * должен работать с определённым типом коллекции. */ private $position = 0; /** * @var bool Эта переменная указывает направление обхода. */ private $reverse = false; public function __construct($collection, $reverse = false) { $this->collection = $collection; $this->reverse = $reverse; } public function rewind() { $this->position = $this->reverse ? count($this->collection->getItems()) - 1 : 0; } public function current() { return $this->collection->getItems()[$this->position]; } public function key() { return $this->position; } public function next() { $this->position = $this->position + ($this->reverse ? -1 : 1); } public function valid() { return isset($this->collection->getItems()[$this->position]); } } /** * Конкретные Коллекции предоставляют один или несколько методов для получения * новых экземпляров итератора, совместимых с классом коллекции. */ class WordsCollection implements \IteratorAggregate { private $items = []; public function getItems() { return $this->items; } public function addItem($item) { $this->items[] = $item; } public function getIterator(): Iterator { return new AlphabeticalOrderIterator($this); } public function getReverseIterator(): Iterator { return new AlphabeticalOrderIterator($this, true); } } /** * Клиентский код может знать или не знать о Конкретном Итераторе или классах * Коллекций, в зависимости от уровня косвенности, который вы хотите сохранить в * своей программе. */ $collection = new WordsCollection(); $collection->addItem("First"); $collection->addItem("Second"); $collection->addItem("Third"); echo "Straight traversal:\n"; foreach ($collection->getIterator() as $item) { echo $item . "\n"; } echo "\n"; echo "Reverse traversal:\n"; foreach ($collection->getReverseIterator() as $item) { echo $item . "\n"; }
Output.txt: Результат выполнения
Straight traversal: First Second Third Reverse traversal: Third Second First
Пример из реальной жизни
Так как PHP уже имеет встроенный интерфейс Итератора, который предоставляет удобную интеграцию с циклами foreach, очень легко создать собственные итераторы для обхода практически любой мыслимой структуры данных.
Этот пример паттерна Итератор предоставляет лёгкий доступ к CSV-файлам.
index.php: Пример из реальной жизни
<?php namespace RefactoringGuru\Iterator\RealWorld; /** * Итератор CSV-файлов. * * @author Josh Lockhart */ class CsvIterator implements \Iterator { const ROW_SIZE = 4096; /** * Указатель на CSV-файл. * * @var resource */ protected $filePointer = null; /** * Текущий элемент, который возвращается на каждой итерации. * * @var array */ protected $currentElement = null; /** * Счётчик строк. * * @var int */ protected $rowCounter = null; /** * Разделитель для CSV-файла. * * @var string */ protected $delimiter = null; /** * Конструктор пытается открыть CSV-файл. Он выдаёт исключение при ошибке. * * @param string $file CSV-файл. * @param string $delimiter Разделитель. * * @throws \Exception */ public function __construct($file, $delimiter = ',') { try { $this->filePointer = fopen($file, 'rb'); $this->delimiter = $delimiter; } catch (\Exception $e) { throw new \Exception('The file "' . $file . '" cannot be read.'); } } /** * Этот метод сбрасывает указатель файла. */ public function rewind(): void { $this->rowCounter = 0; rewind($this->filePointer); } /** * Этот метод возвращает текущую CSV-строку в виде двумерного массива. * * @return array Текущая CSV-строка в виде двумерного массива. */ public function current(): array { $this->currentElement = fgetcsv($this->filePointer, self::ROW_SIZE, $this->delimiter); $this->rowCounter++; return $this->currentElement; } /** * Этот метод возвращает номер текущей строки. * * @return int Номер текущей строки. */ public function key(): int { return $this->rowCounter; } /** * Этот метод проверяет, достигнут ли конец файла. * * @return bool Возвращает true при достижении EOF, в ином случае false. */ public function next(): bool { if (is_resource($this->filePointer)) { return !feof($this->filePointer); } return false; } /** * Этот метод проверяет, является ли следующая строка допустимой. * * @return bool Если следующая строка является допустимой. */ public function valid(): bool { if (!$this->next()) { if (is_resource($this->filePointer)) { fclose($this->filePointer); } return false; } return true; } } /** * Клиентский код. */ $csv = new CsvIterator(__DIR__ . '/cats.csv'); foreach ($csv as $key => $row) { print_r($row); }
Output.txt: Результат выполнения
Array ( [0] => Name [1] => Age [2] => Owner [3] => Breed [4] => Image [5] => Color [6] => Texture [7] => Fur [8] => Size ) Array ( [0] => Steve [1] => 3 [2] => Alexander Shvets [3] => Bengal [4] => /cats/bengal.jpg [5] => Brown [6] => Stripes [7] => Short [8] => Medium ) Array ( [0] => Siri [1] => 2 [2] => Alexander Shvets [3] => Domestic short-haired [4] => /cats/domestic-sh.jpg [5] => Black [6] => Solid [7] => Medium [8] => Medium ) Array ( [0] => Fluffy [1] => 5 [2] => John Smith [3] => Maine Coon [4] => /cats/Maine-Coon.jpg [5] => Gray [6] => Stripes [7] => Long [8] => Large )