Номер телефона

Последнее обновление:

Как удалить непустой каталог при помощи PHP?

Если каталог пустой, для этого можно использовать команду rmdir. А вот как быть, если внутри него что-то есть, т.е. если он - непустой? Тогда вначале нужно удалить это внутреннее и только потом - сам этот каталог. Соответственно, если внутри вложенных каталогов есть еще другие каталоги и/или файлы, то необходимо удалить их все перед тем, как удалять содержащий их (родительский) каталог. Рассмотрим обзор способов и оценим, насколько быстро они выполняются в разных версиях РНР.

Допустим, у нас есть каталог с именем dir общим объемом 35 МБ (на диске), а в нем - примерно 10 тысяч файлов и 10 тысяч каталогов. Те. это такая, достаточно вложенная структура каталогов. Посмотрим, насколько эффективно будет удаляться такой каталог с использованием разных способов. Также посмотрим, насколько различается эффективность в разных версиях PHP. В частности, для PHP 5.3 и РНР 8.1.

1. Использование командной оболочки

Или - командной строки. В Linux:

rm -rf  dir

В Windows:

rmdir /s /q dir

В Linux этот способ не тестировался. В Windows типичное время удаления составило 3,4...6,0 сек.

2. Команда shell_exec

В РНР есть возможность запуска команд из командной оболочки (консольных команд). Например, в Linux подобная команда может звучать следующим образом:

shell_exec('rm -rf ' . escapeshellarg('dir'));

Соответственно, в Windows:

shell_exec('rmdir /s /q ' . escapeshellarg('dir'));

В Linux этот способ не тестировался. В Windows типичное время удаления каталога составило 6,5 сек. в РНР 5.3, 3,0 сек. в РНР 8.1. Однако, в РНР 5.3 иногда некоторые вложенные каталоги НЕ УДАЛЯЛИСЬ сразу. Приходилось запускать эту команду заново. Интересно, что общее время удаления даже меньше, чем при использовании непосредственно командной строки, без PHP. Чем это обусловлено - сложно сказать.

Тогда как в РНР 8.1 такого поведения не замечено. Видимо, что-то было исправлено разработчиками этого языка. Т.е. это - плюс в сторону использования версии 8.1 по сравнению со старой версией.

3. Итераторы RecursiveIteratorIterator и RecursiveDirectoryIterator

Такие возможности появились в РНР 5.0. По сути, эти итераторы внутри содержат реализацию рекурсивных вызовов. Их применение достаточно наглядно:

 function removeDirectory($path) {
if (!is_dir($path)) {
throw new InvalidArgumentException("Путь '$path' не является директорией или не существует.");
}

$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);

foreach ($iterator as $item) {
if ($item->isDir()) {
rmdir($item->getPathname());
} else {
unlink($item->getPathname());
}
}

return rmdir($path);
}

// Запуск:
try {
removeDirectory('dir');
echo "Директория успешно удалена.\n";
} catch (Exception $e) {
echo "Ошибка: " . $e->getMessage() . "\n";
}

Как видим, код достаточно компактный и выразительный. Однако, время его выполнения составило 13,5 сек. в РНР 5.3, 5,6 сек. в РНР 8.1. Т.е. этот способ хуже, чем предыдущие. Хотя, удаляет все сразу, не вынуждает запускать процедуру удаления повторно.

4. Использование функции glob

 function removeSimpleDirectory($path) {
if (!is_dir($path)) {
return false;
}

$files = glob($path . '/*');
foreach ($files as $file) {
is_dir($file) ? removeSimpleDirectory($file) : unlink($file);
}
return rmdir($path);
}
removeSimpleDirectory('dir');

Этот способ проще, чем предыдущий. Однако, время, затраченное на удаление каталога, еще выше и составило примерно 15,0 сек. в PHP 5.3, и 3,6 сек. в РНР 8.1. Т.е. возможно, что этот способ приемлемо поведет себя для каталогов сравнительно небольшой вложенности. В общем же случае его использование едва ли целесообразно. 

5. Использование итератора и нерекурсивного подхода

Если в подходах 3-4 используется рекурсия, то этот способ пытается обойтись без нее. Ведь рекурсия означает откладывание вызовов функции в стек. И до тех пор, пока не будут выполнены зависимые вызовы функции, они так и будут храниться в стеке. И если при сравнительно небольшом числе удаляемых каталогов и/или файлов эта проблема не столь актуальна, то когда их число измеряется десятками-сотнями тысяч могут возникнуть проблемы с оперативной памятью, особенно, если компьютер-сервер, на котором выполняется интерпретатор среды PHP, не является достаточно мощным. Итак:

function removeDirectoryNonRecursive($path) {
if (!is_dir($path)) {
throw new InvalidArgumentException("Путь '$path' не является директорией или не существует.");
}

// Стек для хранения путей к обрабатываемым директориям
$stack = array($path);

while (!empty($stack)) {
$currentDir = array_pop($stack);

// Создаём итератор для текущей директории
$iterator = new FilesystemIterator($currentDir);

$subdirs = array();
$files = array();

// Собираем поддиректории и файлы
foreach ($iterator as $item) {
if ($item->isDir()) {
$subdirs[] = $item->getPathname();
} else {
$files[] = $item->getPathname();
}
}

// Удаляем все файлы в текущей директории
foreach ($files as $file) {
if (!unlink($file)) {
throw new RuntimeException("Не удалось удалить файл: $file");
}
}

// Добавляем поддиректории в стек для последующей обработки
foreach ($subdirs as $subdir) {
$stack[] = $subdir;
}

// После удаления файлов и добавления поддиректорий в стек
// оставляем текущую директорию для удаления на следующем этапе
if (empty($subdirs)) {
// Если в директории не осталось поддиректорий, удаляем её
if (!rmdir($currentDir)) {
throw new RuntimeException("Не удалось удалить директорию: $currentDir");
}
} else {
// Иначе возвращаем её в стек — будем обрабатывать позже
$stack[] = $currentDir;
}
}
}

// Пример использования
try {
removeDirectoryNonRecursive('dir');
echo "Директория успешно удалена.\n";
} catch (Exception $e) {
echo "Ошибка: " . $e->getMessage() . "\n";
}

В итоге, для выполнения этого подхода не хватило стандартного времени в РНР, заданному по умолчанию для выполнения скриптов (30 сек.), после чего скрипт аварийно завершился и в PHP 5.3, и в РНР 8.1. Вот что выдал интерпретатор: Allowed memory size of 134217728 bytes exhausted (tried to allocate 67108872 bytes).

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

6. Использование glob и нерекурсивного подхода

 function removeDirectoryWithGlob($path) {
if (!is_dir($path)) {
throw new InvalidArgumentException("Путь '$path' не существует или не является директорией.");
}

$stack = array($path);

while (!empty($stack)) {
$dir = array_pop($stack);
$items = glob($dir . '/*');

if (empty($items)) {
// Пустая директория — удаляем
rmdir($dir);
continue;
}

$hasSubdirs = false;

foreach ($items as $item) {
if (is_dir($item)) {
$stack[] = $item; // Добавляем поддиректорию в стек
$hasSubdirs = true;
} else {
unlink($item); // Удаляем файл
}
}

// Если поддиректорий не было, удаляем текущую
if (!$hasSubdirs) {
rmdir($dir);
} else {
// Иначе возвращаем в стек для повторного прохода
$stack[] = $dir;
}
}
}
removeDirectoryWithGlob('dir');

Результаты запуска полностью аналогичны предыдущему пункту. Тоже не хватило времени для выполнения. В обоих версиях РНР.

7. Простая рекурсивная функция

 function rrmdir($path) {
$dir = opendir($path);

while(false !== ($file = readdir($dir))) {
if(($file != '.' ) && ( $file != '..' )) {

$next = $path . '/' . $file;
if(is_dir($next)) {
rrmdir($next);
}else{
unlink($next);
}
}
}
closedir($dir);
rmdir($path);
return true;
}

rrmdir('dir');

Это, можно сказать, один из вариантов классического рекурсивного программного кода, идея которого используется не только для удаления, но и, скажем, для обхода каталогов, обладающих вложенной структурой.

Типичное время выполнения в PHP 5.3 составило 7,2 сек, тогда как 4,4 сек. в PHP 8.1. Однако, как и во 2-м варианте, иногда почему-то содержимое удаляемого каталога удалялось не полностью, поэтому приходилось запускать этот программный код неоднократно. Вероятно, это вызвано большой глубиной стека. При этом, однако, никаких ошибок или предупреждений РНР не выдавал, работа кода просто прекращалась через  5...8 сек., да и все. Чего ни разу не наблюдалось при работе итераторов, см. п.3-4. Т.е. при использовании этого программного кода необходимо предусматривать дополнительные проверки качества (полноты) выполнения операции удаления и, при необходимости, ее повтор. 

Обсуждение результатов затрат времени на удаление непустого каталога

Обобщим результаты в таблице, для наглядности:

Вариант PHP 5.3, сек. PHP 8.1, сек.
1. Использование командной оболочки Windows 3,4 3,4
2. Команда shell_exec 6,5 3,0
3. Итераторы RecursiveIteratorIterator и RecursiveDirectoryIterator 13,5 5,6
4. Использование функции glob 15,0 3,6
5. Использование итератора и нерекурсивного подхода - -
6. Использование glob и нерекурсивного подхода - -
7. Простая рекурсивная функция 7,2 4,4

Выводы можно сделать такие:

1. Как в РНР 5.3, так и в РНР 8.1 самым быстрым оказался вариант 2, с использованием команды shell_exec. 

2. Использование итераторов в обоих версиях НЕЦЕЛЕСООБРАЗНО. Возможно, они чисто внешне создают некую "красоту" программного кода или демонстрируют "логику", но этим все преимущества итераторов в данном случае заканчиваются. Это, очевидно, общеизвестные поддержки объектно-ориентированного подхода (ООП), столь любимого нынче многими разработчиками. Абстракции - это дело, конечно, интересно выглядящее (многим нравится), в виде строчек программного кода на мониторе. Также ходят рассуждения, мол, такой код "проще поддерживать". Но, на практике для мало-мальски серьезной работы лучше бы использовать более экономичные и эффективные решения.

3. Использование нерекурсивного подхода НЕЦЕЛЕСООБРАЗНО. По крайней мере, в рамках использованных вариантов. Хотя, казалось бы, рекурсия предполагает достаточно большой объем стека и соответствующие затраты оперативной памяти. Но, тем не менее. 

4. Как и в ряде другой функциональности, в РНР 8.1 оказались сравнимыми по времени выполнения варианты 1, 2, 4 и даже, отчасти, 7. Тогда как в РНР 5.3 более-менее приемлемыми оказались варианты 1, 2, 7.

Какой вариант выбрать в целях кроссплатформенности?

Т.е. чтобы приемлемо работало в обоих версиях РНР. Это, судя по результатам, либо вариант 2, либо 7. Если брать вариант 2, то в другой операционной системе (например, в Linux) придется применять другую команду для командной оболочки. Это - дополнительные усилия, нужно анализировать, в какой именно операционной системе выполняется PHP. 

Поэтому если требования к скорости удаления непустых каталогов - максимальные, стоит использовать вариант 2, т.е. команду shell_exec. Если же есть возможность несколько поступиться скоростью, но зато обеспечить кроссплатформенность, независимость от типа операционной системы, тогда, конечно, оптимальным будет вариант 7, с использованием простой рекурсивной функции rrmdir. Однако, напомним, что при ее использовании стоит делать дополнительные проверки полноты удаления (непустого) каталога и, при необходимости, делать повторение этой операции.


Комментарии:
Всего комментариев:0
Пожалуйста, не забудьте ознакомиться с правилами оставления комментариев.



Подписаться на комментарии на этой странице

Мы можем выполнить

Другие услуги
Интересная и полезная
информация
НАПИШИТЕ НАМ
Яндекс.Метрика
Номер телефона
© Copyright Все права защищены 2013-2026 Научный консалтинг