Использование SPL итераторы

В этой статье вы познакомитесь с итерации и как вы можете воспользоваться некоторыми встроенными классами из Standard PHP Library (SPL). SPL поставляется с большим количеством итераторы (iterators) их можно использовать в коде, что может сделать ваш код более эффективным и в большинстве случаев, более читабельным.

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 () . Если он получает true , то вызов current () , для получения значений в этой точке итерации. Поскольку вы вызываете $key в цикле, PHP вызывает метод key ()   для получения значений ключа. Затем PHP вызывает метод next () для продвижения указателя и вызывает метод valid () еще раз, чтобы проверить достоверность данных. Цикл продолжается до valid () и возвращает false .

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

IteratorAggregate

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

Перепишите пример выше, для того что бы применить IteratorAggregate :



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

    // return an Iterator of your data
    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


В начале итерации, вызывается getIterator () , который возвращает итератор, в этом случае SPL Arraylterator . PHP, делает все необходимые для вызова итератор. В основном, вы передадите данные на внешний итератор, который делает всю работу за вас.

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

Почему и когда использовать SPL Итераторы

Итерации итераторов объектов в основном то же самое что и итерации массивов, и многие программисты задаются вопросом, может было бы проще использовать массивы. Дело в том что, реальная польза итераторов появляется при прохождении большого количества данных или что-то более сложное, чем простой массив.

При обработке больших объемов данных, имеющих большие массивы, копировать каждый раз при использовании их в цикле foreach может быть нежелательным. SPL итератор открывает список и показывает один элемент, что делает их гораздо более эффективными.

При создании данных, итераторы большие, но у них есть ленивая загрузка данных. Ленивая нагрузка здесь просто извлечение необходимых данных, только если и когда это необходимо. Вы также можете управлять (фильтр, преобразование и т.д.) данными, которые работают перед тем как их получит пользователь.

Используйте итераторы по своему усмотрению. Итераторы имеют многочисленные преимущества, но в некоторых случаях (как в небольших наборов массивов) может вызвать нежелательные издержки. Решение о том, когда их использовать, зависит в пригодности в данной ситуации, все эти факторы необходимо учитывать.

Итерации массивов

Для знакомства с итератор возьмём ArrayIterator . Конструктор принимает массив в качестве параметра и предоставляет методы, которые можно использовать для прохода через него.

Вот пример:



<?php
// Массив (с использованием PHP 5.4 'Новая запись сокращенна
$arr = ["pixelcom", "cms", "newshtml5", "php",
    "desig", "article"];

// Создаем новый ArrayIterator и передаем в массив
$iter = new ArrayIterator($arr);

// Цикл по объекту
foreach ($iter as $key => $value) {
    echo $key . ":  " . $value . "<br>";
}


Выход кода выше:



0: pixelcom 
1: cms 
2: newshtml5  
3: php  
4: desig  
5: article


Чаще всего, вы будете использовать  ArrayObject , класс который позволяет работать с объектами, как если бы они были массивами в определенном контексте, и не использовать  ArrayIterator  напрямую. При этом автоматически создается ArrayIterator , когда вы используете цикл foreach или вызвав непосредственно ArrayIterator :: getIterator () .

Обратите внимание, что в то время как  ArrayObject  и  ArrayIterator как массивы, в связи с этим они по-прежнему объекты, пытаются использовать встроенный массив функций, таких как  sort ()  и  array_keys () .

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

Один из распространенных сценариев является цикл  foreach или создать рекурсивную функцию, которая проверяет все элементы многомерного массива.

Например:



<?php
// Многомерный массив
$arr = [
    ["pixelcom", "cms"],
    ["newshtml5", "php"],
    ["desig", "article"],
    "Не является массивом"
];

// Цикл по объекту
foreach ($arr as $key => $value) {
    // Проверяем на массивы
    if (is_array($value)) {
        foreach ($value as $k => $v) {
            echo $k . ": " . $v . "<br>";
        }
    }
    else {
        echo $key . ": " . $value . "<br>";
    }
}


Выход кода выше:



0: pixelcom 
1: cms 
0: newshtml5  
1: php  
0: desig  
1: article
3: не массив


Более элегантный подход использует RecursiveArrayIterator.



<?php
...
$iter = new RecursiveArrayIterator($arr);

// Цикл по объекту
// Нам нужно создать RecursiveIteratorIterator например
foreach(new RecursiveIteratorIterator($iter) as $key => $value) {
    echo $key . ": " . $value . "<br>";
}


Выход, как и в предыдущем примере.

Обратите внимание, что необходимо создать экземпляр RecursiveIteratorIterator и передать его объекту RecursiveArrayIterator , иначе все значения которые вы получите будут в корне массива (и тонны уведомлений в зависимости от настроек).

Вы должны использовать  RecursiveArrayIterator  при работе с многомерными массивами, так как это позволяет вам перебрать текущие записи.  RecursiveIteratorIterator  является  декоратором . Он принимает  RecursiveArrayIterator , и перебирает любой Iterable от начала до конца. По сути, это (выравнивает) RecursiveArrayIterator. Вы можете получить текущую глубину итерации вызвав RecursiveIteratorIterator :: getDepth () . Будьте осторожны с RecursiveArrayIterator  и  RecursiveIteratorIterator , хотя если вы хотите вернуть объекты, объекты рассматриваются как  Iterable  и поэтому будет повторение.

Итерация каталогов

Если нужно пройти через каталог и файлы в определенный момент времени, существуют различные способы достижения этого с помощью встроенной функции предоставляемые в PHP, например, с  scandir ()  или glob() . Но вы также можете использовать  DirectoryIterator . В своей простейшей форме, DirectoryIterator довольно мощный, но он также с подклассами.

Вот пример итерации каталога с DirectoryIterator :



<?php
// create new DirectoryIterator object
$dir = new DirectoryIterator("/my/directory/path");

// loop through the directory listing
foreach ($dir as $item) {
    echo $item . "<br>";
}


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

Например:



.
..
api
index.php
lib
workspace


Не забывайте, что с  DirectoryIterator , и многими другими итераторы SPL, у вас будут дополнительное преимущество использования исключений для обработки ошибок.



<?php
try {
    $dir = new DirectoryIterator("/non/existent/path");
    foreach ($dir as $item) {
        echo $item . "<br>";
    }
}
catch (Exception $e) {
    echo get_class($e) . ": " . $e->getMessage();
}


UnexpectedValueException:
DirectoryIterator::__construct(/non/existent/path,/non/existent/path):
The system cannot find the file specified. (code: 2)

С множеством других методов, таких как  DirectoryIterator :: isDot () ,DirectoryIterator :: GetType ()  и  DirectoryIterator :: getSize () , в значительной степени все свои основные потребности в информации каталога сделаны. Можно даже комбинировать  DirectoryIterator  с  FilterIterator  или  RegexIterator и вернуть файлы, соответствующие определенным критериям. Например:



<?php
class FileExtensionFilter extends FilterIterator
{
    // whitelist of file extensions
    protected $ext = ["php", "txt"];

    // an abstract method which must be implemented in subclass
    public function accept() {
        return in_array($this->getExtension(), $this->ext);
    }
}

//create a new iterator
$dir = new FileExtensionFilter(new DirectoryIterator("./"));
...


SPL также как и RecursiveDirectoryIterator могут быть использованы таким же образом . Функция, которая проходит каталоги рекурсивно, как правило, завалены условной проверкой по каталогам и файлам, а RecursiveDirectoryIterator можете сделать большую часть работы за вас, в результате чего чистый код. Существует один нюанс, RecursiveDirectoryIterator  не возвращают пустые директории, если каталог содержит множество подкаталогов, и нет файлов, то он вернет пустой результат (так же, как ведет себя Git).



<?php
// create new RecursiveDirectoryIterator object
$iter = new RecursiveDirectoryIterator("/my/directory/path");

// loop through the directory listing
// we need to create a RecursiveIteratorIterator instance
foreach (new RecursiveIteratorIterator($iter) as $item) {
    echo $item . "<br>";
}


Вывод напоминает:



. / API / .htaccess  
. / API / index.php  
. / Index.php  ...


Заключение

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

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML -теги и атрибуты: <a href= http://pixelcom.crimea.ua/"" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>