За чистый и ясный код!

Статьи на тему программирования под веб, используя PHP, MySQL, Jquery и многое другое

Рекурсивный обход директории с помощью итераторов

Ноябрь15
Обращаю Ваше внимание на загородную недвижимость в области, найдите себе достойное жильё!

рекурсия с помощью итераторовКак-то на работе нужно мне было обойти директорию и удалить все файлики в ней, юзать для этого консоль я не мог. Все надо было делать на чистом 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%

Как видно нативные фичи всегда рулят!

Статья просмотренна 97214 раз, зашло посетителей 26875

рубрика: PHP

10 комментариев в “Рекурсивный обход директории с помощью итераторов”

  1. Avatar
    @terranisu пишет:

    Конечно все зависит от поставленных задач, но RecursiveDirectoryIterator сильно начинает тормозить при глубине вложения директорий, хотя бы 3. Причем, оставание начинает рости в геометрической прогрессии при увеличении степени вложенности директорий.
    Не знаю, как дела обстоят в php5.3.3 (как-то задач подобных не попадалось), но в свое время тестировал на php5.3.1 и все было довольно плохо.
    Т.е. если вам не критично время выполнения скрипта и директорий не много, то использование RecursiveDirectoryIterator оправданно. Все просто и лаконично. Иначе, стоит 100 раз подумать прежде, чем использовать.


  2. Avatar
    maxnag пишет:

    Как-то не заметил. Проверю, самому интересно.

    Спасибо за каммент


  3. Avatar
    загадки пишет:

    спасибо за толковые выкладки. а где можно применить рекурсивные функции подобного рода, кроме как под конкретную задачу?


  4. Avatar
    maxnag пишет:

    Я всегда её применял для удаления данных в каталоге, х.з. какой уровень вложенности у тебя будет.
    Больше я нигде не применял подобного рода ф-ции.

    В БД при работе с камментами я не юзаю рекурсию, это очень наклдадно, использую другие известные методы.


  5. Avatar
    Replikon пишет:

    Спасибо за ценную инфу на русском!


  6. Avatar
    bosha пишет:

    Надо различать задачи.
    opendir — возвращает список файлов в директории.
    RecursiveDirectoryIterator — возвращает ОБЪЕКТЫ. У которых есть свойства (размер, тип, права, владелец, группа и т.д.).
    Если необходимо всё это, то костыли с opendir займут куда больше процессорного времени. Рекомендую добавить это в статью, и не вводить людей в заблуждение.


  7. Avatar
    maxnag пишет:

    Мне кажется я четко описал, кто есть кто

    К родным (native) методам я отнес функции opendir(), closedir(), readdir() и rewinddir(). Больше казать об этих ф-циях нечего, чистая классика.

    и

    Класс DirectoryIterator реализует интерфейс итератора …. Методы класса DirectoryIterator()

    Тут чётко видно, кто есть ф-ция, а кто класс с методами? Что не так с Вашей точки зрения я не так сделал?


  8. Avatar
    bosha пишет:

    Да, видно. Было бы странно, если бы было непонятно, но проведённые тесты не совсем верны по описаным выше причинам. Попробуйте что нибудь такое сделать на родных методах, и с помощью DirectoryIterator:
    https://dl.dropbox.com/u/585714/screenshots/Selec

    Результаты, я думаю, Вас удивят.:)


  9. Avatar
    Юрий пишет:

    Что за дебильная привычка именовать переменные вроде idir? Что в этом сокращении содержится? Мы-ж не криптографы, а программисты. Правильно directoryIterator


  10. Avatar
    maxnag пишет:

    Это не "дебильная привычка" — это опыт, например я понятие не имею какой тип данных в этой переменной, а по её названию я, иногда, могу понять, что i — это iterator. Также когда юзаешь название класса, у которого есть интерфейс часто пишут IClassName и первая буква означает Interface.

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

    Рекомендую ознакомиться с этим https://svyatoslav.biz/misc/psr_translation/


не публикуется

пример

Оставить комментарий или два:

  

Облако тегов

cli csv dump events form Kohana locale models MySQL mysqldump orm PHP tools trigger validate газ газовый счетчик итоги кеширование переменные

Облако тегов плагина WP Cumulus для WordPress требует для просмотра Flash Player 9 или выше.

Я на твиттере!

  • у твиттера тоже бывают перерывы...

Календарь

Ноябрь 2010
Пн Вт Ср Чт Пт Сб Вс
« Окт   Дек »
1234567
891011121314
15161718192021
22232425262728
2930  

Сейчас на сайте