Итератор — это поведенческий паттерн, позволяющий последовательно обходить сложную коллекцию, без раскрытия деталей её реализации.
Благодаря Итератору, клиент может обходить разные коллекции одним и тем же способом, используя единый интерфейс итераторов.
Концептуальный пример
Этот пример показывает структуру паттерна Итератор , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире 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
)