Статьи

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

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

Если вы используете PHP, вы, скорее всего, использовали массивы. И если вы использовали массивы, то, безусловно, вы просматривали его элементы. Просмотрите любой код, и почти наверняка вы найдете цикл foreach . Да, итерация — это просто процесс обхода списка значений. Итератор — это объект, который пересекает список, будь то массив, список каталогов или даже набор результатов базы данных.

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

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

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

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

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

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

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

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

 <?php // an array (using PHP 5.4's new shorthand notation) $arr = ["sitepoint", "phpmaster", "buildmobile", "rubysource", "designfestival", "cloudspring"]; // create a new ArrayIterator and pass in the array $iter = new ArrayIterator($arr); // loop through the object foreach ($iter as $key => $value) { echo $key . ": " . $value . "<br>"; } 

Вывод вышеуказанного кода:

  0: точка сайта
 1: phpmaster
 2: buildmobile
 3: Rubysource
 4: дизайнфестиваль
 5: облака 

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

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

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

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

 <?php // a multidimensional array $arr = [ ["sitepoint", "phpmaster"], ["buildmobile", "rubysource"], ["designfestival", "cloudspring"], "not an array" ]; // loop through the object foreach ($arr as $key => $value) { // check for arrays if (is_array($value)) { foreach ($value as $k => $v) { echo $k . ": " . $v . "<br>"; } } else { echo $key . ": " . $value . "<br>"; } } 

Вывод вышеуказанного кода:

  0: точка сайта
 1: phpmaster
 0: buildmobile
 1: Rubysource
 0: дизайнфестиваль
 1: облака
 3: не массив 

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

 <?php ... $iter = new RecursiveArrayIterator($arr); // loop through the object // we need to create a RecursiveIteratorIterator instance foreach(new RecursiveIteratorIterator($iter) as $key => $value) { echo $key . ": " . $value . "<br>"; } 

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

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

Вы должны использовать RecursiveArrayIterator при работе с многомерными массивами, так как он позволяет вам перебирать и текущую запись, но оставляет это на ваше усмотрение. RecursiveIteratorIterator — это декоратор, который делает это за вас. Он берет RecursiveArrayIterator , итерирует по нему и итерирует по любой Iterable записи 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>"; } 

Очевидно, что вывод будет зависеть от указанного вами пути и содержимого каталога. Например:

  ,
 ..
 апи
 index.php
 Lib
 Рабочее пространство 

Не забывайте, что с 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 :: __ конструкция (/ не / существует / путь, / не существует / путь): система не может найти указанный файл.  (код: 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 который можно использовать так же, как RecursiveArrayIterator . Функция, которая рекурсивно пересекает каталоги, обычно будет завалена условными проверками на наличие допустимых каталогов и файлов, и 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 — это «стандартная» библиотека. Иногда вы можете найти классы слишком общими, и они не всегда делают то, что вам нужно. В таких случаях вы можете легко расширить классы, чтобы добавить свои собственные функции или настроить существующие функции по мере необходимости. В следующей части этой серии я покажу вам, как использовать интерфейсы SPL для создания ваших собственных пользовательских классов, которые можно просматривать и использовать как массивы.

Изображение через Mushakesa / Shutterstock