Рекурсивный обход директории с помощью итераторов
Как-то на работе нужно мне было обойти директорию и удалить все файлики в ней, юзать для этого консоль я не мог. Все надо было делать на чистом php. Удалить просто не пустую директорию, как Вы знаете нельзя, надо удалить всё, что в ней, а потом удалить её.
Для таких целей используется рекурсия.
Я попытался разобраться в этом вопросе, и всё что узнал делюсь.
Сначала я просто попытался пройтись по одной директории не исполmзуя рекурсию разными методами для определения удобства использования и скорости работы.
Проход директории с помощью родных методов
К родным (native) методам я отнес функции opendir(), closedir(), readdir() и rewinddir(). Больше казать об этих ф-циях нечего, чистая классика. Открыли каталог (если он существует), получили дескриптор (указатель) на него и начинаем с ним работать, по окончанию желательно закрыть каталог.
К родным методам я отношу те, которые существует в голом ядре, т.е. если отключить все расширения и эти ф-ции, классы существуют, то я их называю родными или нативными. Например класс или ф-ции по работе с Memcache существуют если подключено соответствующее расширение, следовательно такие вещи родными назвать нельзя.
$dir = 'c:\\windows\\system32'; $odir = opendir($dir); while (($file = readdir($odir)) !== FALSE) { if ($file != '.' && $file != '..') { echo $file.'<br>'; } } closedir($odir);
Проход директории с помощью предопределенного класса dir()
Предопределенных классов довольно много, они или являются родными, как класс dir(), Exception, Reflection, или такие которые подключаются с помощью соот. библиотек — mysqli, curl, GD и тд
$cat = dir($dir); while (($file = $cat->read()) !== FALSE) { if ($file != '.' && $file != '..') { echo $file.'<br>'; } } $cat->close();
Методы класса dir()
Название метода | Описание метода |
path | путь к директории |
handle | ресурс, дескриптор |
close() | закрыть директорию |
rewind() | сброс дескриптора в начало директории |
read() | Чтение одного элемента директории и передвигаем указатель на одну позицию вниз. |
Проход директории с помощью Итератора (DirectoryIterator)
Что есть итератор хорошо описано в Википедии не буду копи-пастить…
Класс DirectoryIterator реализует интерфейс итератора (могут проходить коллекцию в цикле foreach).
$idir = new DirectoryIterator($dir); foreach($idir as $file) { if ($file != '.' && $file != '..') { echo $file->__toString().'<br>'; } }
У данного класса уж очень много методов для работы с файлами и/или директориями. Постараюсь их все здесь описать. Некоторые из приведенных ниже относятся только в Unix подобным системам.
Методы класса DirectoryIterator()
Название метода | Описание метода |
getFilename() | возврат имени файла или поддиректории |
getBasename() | похож на getFilename(), но может удалять суфикс,если таковой передать в виде параметра * |
isDot() | Определяет является ли текущий элемент «.» или «..» |
rewind() | сброс указателя на первый элемент |
valid() | проверка является ли текущий элемент правильным файлом. Честно не понял. |
key() | возврат ключа текущего элемента |
current() | возврат текущего элемента |
next() | на 1 шаг вперед передвигает указатель |
__toString() | оопшный метод, приводит свойство к строке |
getPath() | возврат просто имени директории/файла и все |
getPathname() | возврат пути к файлу/директории+само название |
getPerms() | возврат прав доступа только для UNIX |
getInode() | х.з. что это, судя по названи наверное какое-то имя узла, думаю только для UNIX |
getSize() | размер файла в байтах, для директории всегда ноль |
getOwner() | возврат имя владельца, только для UNIX |
getGroup() | возврат ИД группы, только для UNIX |
getATime() | последний доступ к файлу/директории в сек (начало с 1970) |
getMTime() | последний модификации файла/директории в сек (начало с 1970) |
getCTime() | последний изменения к файла/директории в сек (начало с 1970) |
getType() | возрат dir или file для сотв элемента. |
isWritable() | думаю понятно из названия, возврат истина/ложь |
isReadable() | думаю понятно из названия, возврат истина/ложь |
isExecutable() | думаю понятно из названия, возврат истина/ложь |
isFile() | думаю понятно из названия, возврат истина/ложь |
isDir() | думаю понятно из названия, возврат истина/ложь |
isLink() | думаю понятно из названия, возврат истина/лож,только для UNIX |
getLinkTarget() | для данных методов не нашел описание даже на оф.сайте. |
getRealPath() | для данных методов не нашел описание даже на оф.сайте. |
getFileInfo() | для данных методов не нашел описание даже на оф.сайте. |
getPathInfo() | для данных методов не нашел описание даже на оф.сайте. |
openFile() | для данных методов не нашел описание даже на оф.сайте. |
setFileClass() | для данных методов не нашел описание даже на оф.сайте. |
setInfoClass() | для данных методов не нашел описание даже на оф.сайте. |
* — за подробностями обращайтесь к официальной документации.
Рекурсивный обход директории с помощью родных методов
Рассмотрев как можно пройтись по директории теперь рассмотри как можно пройтись абсолютно по всему каталогу.
function recursive($dir) { static $deep = 0; $odir = opendir($dir); while (($file = readdir($odir)) !== FALSE) { if ($file == '.' || $file == '..') { continue; } else { echo str_repeat('---', $deep).$dir.DIRECTORY_SEPARATOR.$file.'<br>'; } if (is_dir($dir.DIRECTORY_SEPARATOR.$file)) { $deep ++; recursive($dir.DIRECTORY_SEPARATOR.$file); $deep --; } } closedir($odir); } recursive($dir);
При запуске данной ф-ции она пройдет абсолютно по всем (. и .. не включаем по внимание) директориям и файлам и нарисует дерево. Данная функция мне не понравилась, что она сильно громоздкая и я все же больше склоняюсь к ООП.
Рекурсивный обход директории с помощью итератора (RecursiveDirectoryIterator)
Решение для обхода каталога на ООП нашлось и его скрипт ниже.
$rdir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), TRUE); foreach ($rdir as $file) { echo str_repeat('---', $rdir->getDepth()).$file.'<br>'; }
Красиво, всего одна строка, два класса и полный набор данных.
Из существующий методов , которые я уже не привожу, т.к. многие уже описал в других классах (key, current, rewind, valid etc), хотелось бы подчеркнуть метод setMaxDepth(integer); — он позволяет задать глубину прохода.
Другие методы которые есть у данного класса даже не описаны на оф. сайте.
Тесты
Как я их проводил. В цикле (1000) я засекал время перед открытие директории и после её закрытия. Затем я высчитывал среднее арифметическое. Т.к. результаты сильно зависят от железа (винчестера и др параметров), то приводить просто время в секундах не кошерно, поэтому я перевел их проценты. За 100% я принял время работы родных функций.
Проход 1000 раз директории $dir = ‘c:\\windows\\system32’;
native — 100%
dir() — 107%
DirectoryIteratior — 115%
Рекурсия директории $dir = ‘c:\\windows\\system32’;
native — 100%
RecursiveDirectoryIterator — 115%
Как видно нативные почти всегда рулят, но и не так далеко отстают ООПшные фичи.
Кто что будет юзать — уж дело личное.
UPD
Прошу прощения у общественности, я не верно провел тест с рекурсивным обходом.
Вот более детальные данные.
native — 100%
RecursiveDirectoryIterator — 185%
Как видно нативные фичи всегда рулят!
Статья просмотренна 97031 раз, зашло посетителей 26812
Конечно все зависит от поставленных задач, но RecursiveDirectoryIterator сильно начинает тормозить при глубине вложения директорий, хотя бы 3. Причем, оставание начинает рости в геометрической прогрессии при увеличении степени вложенности директорий.
Не знаю, как дела обстоят в php5.3.3 (как-то задач подобных не попадалось), но в свое время тестировал на php5.3.1 и все было довольно плохо.
Т.е. если вам не критично время выполнения скрипта и директорий не много, то использование RecursiveDirectoryIterator оправданно. Все просто и лаконично. Иначе, стоит 100 раз подумать прежде, чем использовать.
Как-то не заметил. Проверю, самому интересно.
Спасибо за каммент
спасибо за толковые выкладки. а где можно применить рекурсивные функции подобного рода, кроме как под конкретную задачу?
Я всегда её применял для удаления данных в каталоге, х.з. какой уровень вложенности у тебя будет.
Больше я нигде не применял подобного рода ф-ции.
В БД при работе с камментами я не юзаю рекурсию, это очень наклдадно, использую другие известные методы.
Спасибо за ценную инфу на русском!
Надо различать задачи.
opendir — возвращает список файлов в директории.
RecursiveDirectoryIterator — возвращает ОБЪЕКТЫ. У которых есть свойства (размер, тип, права, владелец, группа и т.д.).
Если необходимо всё это, то костыли с opendir займут куда больше процессорного времени. Рекомендую добавить это в статью, и не вводить людей в заблуждение.
Мне кажется я четко описал, кто есть кто
и
Тут чётко видно, кто есть ф-ция, а кто класс с методами? Что не так с Вашей точки зрения я не так сделал?
Да, видно. Было бы странно, если бы было непонятно, но проведённые тесты не совсем верны по описаным выше причинам. Попробуйте что нибудь такое сделать на родных методах, и с помощью DirectoryIterator:
https://dl.dropbox.com/u/585714/screenshots/Selec…
Результаты, я думаю, Вас удивят.:)
Что за дебильная привычка именовать переменные вроде idir? Что в этом сокращении содержится? Мы-ж не криптографы, а программисты. Правильно directoryIterator
Это не "дебильная привычка" — это опыт, например я понятие не имею какой тип данных в этой переменной, а по её названию я, иногда, могу понять, что i — это iterator. Также когда юзаешь название класса, у которого есть интерфейс часто пишут IClassName и первая буква означает Interface.
Ты можешь называть как угодно, но когда ты работаешь в команде, в большой компании есть этика и разные рекомендации по наименованию всего и вся и этому надо следовать.
Рекомендую ознакомиться с этим https://svyatoslav.biz/misc/psr_translation/