Использование итераторов SPL в PHP, часть 2






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

Для того, чтобы объект мог быть итерирован в цикле foreach, для PHP необходимо, чтобы этот объект реализовывал интерфейс Traversable. Однако, вы не можете реализовывать этот интерфейс непосредственно, и вместо этого должны использовать любой из двух дочерних интерфейсов: Iterator или IteratorAggregate.

Итератор

Iterator

Интерфейс Iterator предназначен для реализации классами, объекты которых могут быть интерированы непосредственно или использоваться для создания внешних итераторов. Интерфейс определяет пять методов для реализации: rewind(), current(), key(), next() и valid().

Предположим, у нас есть библиотека книг и мы хотим иметь возможность выполнять итерацию по всей коллекции книг. Ниже приведён пример класса, реализующего Iterator, который решает подобную задачу:

<?php
class Library implements Iterator
{
    // Внутренний указатель на текущую позицию указателя в коллекции
    protected $position = 0;

    // Данные
    protected $books = [
        "Professional PHP Programming",
        "Programming Perl",
        "A Byte of Python",
        "The Ruby Way"
    ];

    // Этот метод устанавливает внутренний указатель на начало коллекции
    // и используется для начала итерации
    public function rewind() {
        echo "rewinding <br>";
        $this->position = 0;
    }

    // Этот метод возвращает текущее значение указателя
    public function current() {
        echo "current <br>";
        return $this->books[$this->position];
    }

    // Этот метод возвращает текущую позицию указателя
    public function key() {
        echo "key <br>";
        return $this->position;
    }

    // Этот метод сдвигает указатель на следующий элемент
    public function next() {
        echo "next <br>";
        ++$this->position;
    }

    // Этот метод проверяет, есть ли данные относительно текущей позиции указателя
    public function valid() {
        echo "valid <br>";
        return isset($this->books[$this->position]);
    }
}
$library = new Library();
foreach ($library as $key => $value) {
    echo $key . ": " . $value . "<br>";
}

Вывод кода будет следующим:

rewinding
valid
current
key
0: Professional PHP Programming
next
valid
current
key
1: Programming Perl
next
valid
current
key
2: A Byte of Python
next
valid
current
key
3: The Ruby Way
next
valid

Перед началом итерации PHP вызывает метод rewind(), чтобы установить указатель на начало данных. Затем при помощи вызова метода valid() он проверяет, есть ли данные по текущей позиции указателя. Если в ответ на вызов этого метода PHP получает true, то следующим шагом будет вызван метод current(), который вернёт данные, находящие относительно текущей позиции указателя. Если вы запросите ключ в конструкции foreach, то интерпретатор будет обращаться к методу key() вашего класса, чтобы получить его. Таким образом, итерация будет продолжнать до тех пор, пока объект не вернёт false в ответ на вызов метода valid().

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

IteratorAggregate

IteratorAggregate требует реализации лишь одного метода, getIterator(). Этот метод должен возвращать внешний итератор, который и будет использоваться для выполнения итераций. Переписав наш пример с использованием IteratorAggregate, получим следующее:

<?php
class Library implements IteratorAggregate {
    protected $books = [
        "Professional PHP Programming",
        "Programming Perl",
        "A Byte of Python",
        "The Ruby Way"
    ];

    // Возвращаем Iterator
    public function getIterator() {
        echo "getIterator <br>";
        return new ArrayIterator($this->books);
    }
}
$library = new Library();
foreach($library as $key => $value) {
    echo $key . ": " . $value . "<br>";
}

В результате выполнения получим:

getIterator
0: Professional PHP Programming
1: Programming Perl
2: A Byte of Python
3: The Ruby Way

Перед началом итераций интерпретатор PHP вызывает метод getIterator(), который возвращает Iterator; в нашем случае это ArrayIterator. А дальше работа происходит уже с полученным итератором таким образом, как это описано выше. То есть, вы передаёте свои данные внешнему итератору, он делает всю работу, а вы спокойно курите в сторонке.

Поскольку оба интерфейса выполняют одну и ту же задачу, иногда бывает трудно определиться, что же нужно использовать в конкретной ситуации. Правило большого пальца гласит: используйте Iterator тогда, когда вам необходимо создать сложный итератор «с нуля», с вашим собственным блэкджеком и куртизанками. В остальных случаях не морочьте голову и пользуйтесь готовыми SPL итераторами, заворачивая в них данные и реализуйте интерфейс IteratorAggregate.

Итоги

В этой статье мы рассмотрели два SPL-интерфейса. предназначенных для создания собственных классов-итераторов. Я надеюсь, что вы смогли разобраться, понять всю простоту принципов работы итераторов и вскоре начнёте применять полученные знания в ваших проектах.

Источник: SitePoint