Массивы и итераторы (часть 2)

В прошлой части мы поговорили вкратце об итераторах – о том как и зачем ими пользоваться.
В этой части я расскажу как он устроен изнутри, как написать свой итератор, и о разных типах и множестве возможностей этого чудесного инструмента.

Итератор это интерфейс от которого мы наследуемся – вот он.

interface Iterator extends Traversable {
    abstract public mixed current ( void )
    abstract public scalar key ( void )
    abstract public void next ( void )
    abstract public void rewind ( void )
    abstract public boolean valid ( void )
}

Разберемся поподробнее зачем нужна каждый из этих методов.

  • current – Получает текущий элемент
  • key – получает ключ элемента
  • next – переходит к следующему элементу
  • rewind – метод вызывающийся в самом начале разбора (например первый цикл foreach)
  • valid – проверяет элемент на валидность (чаще всего используется для определения конца перебора)

Вроде все просто, но в примере из прошлой статьи у нас было

foreach($array as $key => $item){}

И никаких вызовов этих функций.

Все просто они вызываются автоматически в таком порядке.

  1. В первую итерацию цикла вызывается rewind.
    Эта функция вызывается самой первой (например устанавливает текущий индекс массива на 0)
  2. После идет valid – проверка на валидность данных
  3. current – присваивает переменной $item текущее значение итератора
    После этого мы можем работать с $item как с текущим элементов
  4. key – присваивает текущий ключ переменной $key
    После этого мы можем работать с $key – например узнать текущий ключ массива
  5. При последующих итерация все тоже самое, но функция rewind заменяется на next

 

Если не понятно, то сейчас я опишу все это дело непосредственно в коде

foreach($array as $key => $item){ 
    // Если $key == 0 (первая итерация) $array->rewind() 
    // Если $key == 0 (первая итерация) $array->next()
    // $array->valid()
    // $array->current()
    // теперь мы можем получить $item
    var_dump($item);
    // $array->key()
    // теперь мы можем получить $key
    var_dump($key);
}

Именно в таком порядке они и вызываются.

Ну что, проясняется?
Теперь попробуем написать свой итератор. Итератор будет простой – массив с диапазоном чисел (аналог range())

Для начала создадим класс и определим все необходимые функции.

class Range implements Iterator {
    public function current(){}
    public function key(){}
    public function next(){}
    public function rewind(){}
    public function valid(){}
}

А теперь приступим к их определению.
По нашей задумке итератор будет представлять собой массив из чисел начиная с минимального и заканчивая максимальным.

Для определения этих чисел будем использовать конструктор.

class Range implements Iterator {
    private $min = 0;
    private $max = 0;
    public function __construct($min, $max){
        $this->min = (int)$min;     
        $this->max = (int)$max;
    }
    public function current(){}
    public function key(){}
    public function next(){}
    public function rewind(){}
    public function valid(){}
}

Теперь, нам нужно определиться с ключом – добавим переменную которая будет отвечать за значение ключа.

private $index = 0;

Именно ее мы будем изменять в соответствии с текущим элементом.

Как вы помните, первая функция которая вызывается – rewind. Определим ее

public function rewind(){
    $this->index = 0;
}

Обнуляем индекс, и все. Больше нам от нее ничего не нужно.
Идем дальше – следующая функция valid. С помощью нее проверяем не превысили ли мы максимальное число ($this->max)

public function valid(){  
    // Прибавляем индекс (текущий шаг) к минимальному числу, чтобы узнать текущее число.
    // Это число не должно быть больше максимального.
    return $this->index + $this->min <= $this->max;
}

Следующая по списку функция – current.
Как вы помните, она возвращает текущее значение при переборе (задает переменную $item)

public function current(){
    // Добавляем к минимальному значению номер итерации (текущий ключ)
    return $this->min + $this->index;
}

С функцией key все просто – она возвращает значение ключа

public function key(){
    return $this->index;
}

Все. Первая итерация закончилась, а при последующих, в самом начале, функция rewind меняется на next. Осталось определить только ее.
В ней мы будем увеличивать текущий ключ на один.

public function next(){
    $this->index ++;
}

А теперь, если мы все это дело совместим, то получим следующий, рабочий код.

class Range implements Iterator {
    private $min = 0;
    private $max = 0;
    private $index = 0;

    public function __construct($min, $max){
        $this->min = (int)$min;
        $this->max = (int)$max;
    }
    public function current(){
        return $this->min + $this->index;
    }
    public function key(){
        return $this->index;
    }
    public function next(){
        $this->index ++;
    }
    public function rewind(){
        $this->index = 0;
    }
    public function valid(){
        return $this->index + $this->min <= $this->max;
    }
}
$obj = new Range(5,14);
foreach($obj as $key => $item){
    echo $key . ' => ' . $item . "n";
}

Как видите мы создали массив из 10 элементов (5-14 включительно)
Этот код выведет нам следующее

0 => 5
1 => 6
2 => 7
3 => 8
4 => 9
5 => 10
6 => 11
7 => 12
8 => 13
9 => 14

И, как видите, не нарушился ни порядок элементов, ни порядок ключей.
Конечно, этот код просто образовательный, и достаточно бесполезный, хотя бы потому что в нем нет проверок и во многих случаях скрипт будет падать или просто не работать, но он наглядно показывает что и как происходит внутри итераторов.

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

[Всего голосов: 0    Средний: 0/5]

Добавить комментарий

Ваш e-mail не будет опубликован.