Когда я впервые столкнулся с термином «итерация» и увидел огромный список классов, связанных с ним в 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