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






Когда я впервые наткнулся на термин «итерация» и увидел огромное количество классов, связанных с этим термином, я опешил. Мне показалось, что всё это слишком сложно, чтобы разобраться. Но совсем скоро я понял, что «итерация» — это всего лишь умное словечко, чтобы описать то, что программисты используют каждый день.

Если вы PHP-разработчик, вы однозначно пользовались массивами. А если вы пользовались массивами, то ещё более однозначно, что вы выполняли перебор их элементов в циклах. Взгляните на любой кусок кода и с огромной долей вероятности вы увидите там цикл foreach. Так вот, итерация — это всего лишь процесс обхода списка значений, а итератор — это объект, который выполняет сам процесс, будь то обход массива, списка файлов или даже результатов выборки из таблицы БД.

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

Итератор

Где и когда использовать итераторы SPL

Как вы скоро увидите, работа с итераторами примерно напоминает работу с массивами и отчасти по этой причине многие люди задаются вопросом: зачем вообще нужны эти итераторы, если есть массивы? Реальную выгоду от использования итераторов можно получить в случаях, когда нужно итерировать большие объёмы данных, а также данные, которые по своей структуре сложнее обычного массива.

Цикл foreach создаёт копию полученного массива. Если вы имеете дело с большими объёмами данных, такой подход не годится по очевидной причине: снижение производительности. Итератор SPL работает по другому: он обрабатывает один элемент итерируемого списка за раз, делая это куда более эффективно, нежели foreach.

При создании поставщиков данных (data providers) итераторы помогают сделать их более эффективными, предлагаю возможности ленивой загрузки (lazy loading). «Ленивая загрузка» означает то, что фактическое получение данных из источника выполняет только тогда, когда эти данные нужны. Помимо прочего, вы получаете возможность трансформации данных перед тем, как отдавать их клиенту объекта.

Однако ответ на вопрос «использовать итераторы или нет» остаётся полностью результатом вашего выбора. Итераторы имею массу полезных свойств, но в некоторых случаях (в частности, с маленькими объёмами данных) затраты на их применение будут неоправданными. В любом случае, выбор остаётся за вами. Всегда старайтесь рассмотреть максимальное количество факторов, прежде чем принимать решение.

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

Первый итератор, который я вам хочу представить — это ArrayIterator. Его конструктор принимает массив в качестве параметра, а сам объект предоставляет методы для выполнения итерационных операций. Вот пример работы с ArrayIterator:

В результате получим следующий вывод:

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

Имейте ввиду, что хотя ArrayObject и ArrayIterator ведут себя подобно массивам в контексте foreach, они всё-таки являются объектами. И если вы попытаетесь использовать с ними встроенные функции PHP вроде sort() and array_keys(), вы получите ошибку.

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

Традиционным решением для обхода многомерных массивов можно считать вложенные циклы. Например:

Результатом работы приведённого кода будет:

С использованием RecursiveArrayIterator можно добиться более элегантного решения:

Вывод этого кода будет таким же, как и в предыдущем случае.

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

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

Итерация каталога файловой системы

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

Неплохо для двух строчек кода? И не забывайте, что вдобавок вы теперь можете пользоваться исключениями:

Используя множество полезных методов вроде DirectoryIterator::isDot(), DirectoryIterator::getType() и DirectoryIterator::getSize(), вы можете получить практически всю информацию, которая только потребуется.

Если пойти дальше, то вы можете воспользоваться DirectoryIterator в связке с FilterIterator или RegexIterator, чтобы фильтровать результаты так. как вам нужно. Например:

SPL также предлагает RecursiveDirectoryIterator, который может быть использовать в точности, как и RecursiveArrayIterator. В отличие от встроенных функций, RecursiveDirectoryIterator делает множество дополнительной работы за вас, что позволяет создавать более чистый и понятный код. Заранее хочу вас предупредить об одной особенности: RecursiveDirectoryIterator не возвращает пустых каталогов. Даже если каталог содержит вложенные подкаталоги, но ни в одном из них не будет файла, то такой каталог считается пустым и не будет возвращён (в точности так же ведёт себя Git).

Итоги

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

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

Источник: SitePoint