Работа с файловой системой: функции для каталогов в PHP

Раздел: Работа с файлами -> Работа с файлами

Работа с каталогами в PHP: основные подходы и практические примеры

Наиболее эффективное решение для стандартных операций с директориями

Для создания, проверки существования, чтения и удаления каталогов в PHP используются встроенные функции: mkdir(), is_dir(), scandir(), rmdir(). Этот набор покрывает большинство повседневных задач.


// Создание одиночной директории
mkdir('/path/to/dir', 0755);

// Проверка существования
if (is_dir('/path/to/dir')) { /* ... */ }

// Чтение содержимого
$items = scandir('/path/to/dir');

// Удаление пустой директории
rmdir('/path/to/dir');
  

Пояснение: mkdir() принимает путь и права доступа (по умолчанию 0777 с учётом umask). scandir() возвращает массив с записями '.' и '..'. rmdir() удаляет только пустую директорию.

Типичные ошибки и их решения:

  • Ошибка: mkdir(): Permission denied. Решение: Проверить права на родительский каталог или использовать абсолютный путь.
  • Ошибка: rmdir(): Directory not empty. Решение: Предварительно удалить содержимое (например, рекурсивной функцией).
  • Ошибка: scandir(): failed to open dir. Решение: Проверить существование и права доступа к каталогу.

Цель использования: Быстрое и простое выполнение одиночных операций без вложенных структур.

Как создать вложенные директории одной командой без ручного создания каждой папки?

Используйте параметр recursive функции mkdir().


mkdir('/path/to/new/deep/folder', 0755, true);
  

Пояснение: Третий аргумент true включает рекурсивное создание. Все промежуточные каталоги будут созданы автоматически, если их нет.

Проблема: Функция может вернуть false, если каталог уже существует и параметр recursive не задан. Решение: Предварительно проверять is_dir() или установить

Также возможна проблема с правами: каталог может создаться с неправильными правами из-за umask. Решение: После создания изменить права chmod().

Случаи использования: Создание временных структур, установка CMS, резервное копирование.

Как проверить, существует ли папка, и создать её при отсутствии в одной связке?

Комбинация is_dir() и mkdir().


$dir = '/path/to/folder';
if (!is_dir($dir)) {
    mkdir($dir, 0755);
}
  

Пояснение: Проверяем, является ли путь директорией. Если нет, создаём. Учитывайте, что file_exists() тоже подходит, но может вернуть true для файла с таким именем.

Проблема: Гонка состояний (race condition) между проверкой и созданием. Решение: В PHP нет атомарной операции, но можно попробовать создать с mkdir() и подавить ошибку оператором '@', затем проверить is_dir().

Случаи использования: Гарантия наличия каталога перед записью файлов, инициализация проекта.

Как удалить папку вместе со всем её содержимым (рекурсивное удаление)?

Встроенная функция rmdir() не поддерживает рекурсию. Необходимо написать свою рекурсивную функцию.


function deleteDir($path) {
    if (!is_dir($path)) return;
    $items = array_diff(scandir($path), ['.', '..']);
    foreach ($items as $item) {
        $itemPath = $path . DIRECTORY_SEPARATOR . $item;
        if (is_dir($itemPath)) {
            deleteDir($itemPath);
        } else {
            unlink($itemPath);
        }
    }
    rmdir($path);
}
  

Пояснение: Функция обходит все элементы, удаляет файлы и рекурсивно вызывает себя для подкаталогов, затем удаляет пустую текущую директорию.

Проблема: Ошибка при отсутствии прав на удаление. Решение: Проверять is_writable() перед операциями.

Проблема: Бесконечная рекурсия при символических ссылках, указывающих на родительский каталог. Решение: Проверять is_link() и не следовать по ссылкам.

Случаи использования: Очистка временных данных, удаление кэша, деинсталляция модулей.

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

Используйте scandir() с фильтрацией или opendir()/readdir().


// Через scandir
$all = scandir('/path');
$items = array_diff($all, ['.', '..']);

// Через opendir
$dh = opendir('/path');
$items = [];
while (($entry = readdir($dh)) !== false) {
    if ($entry !== '.' && $entry !== '..') {
        $items[] = $entry;
    }
}
closedir($dh);
  

Пояснение: Первый способ короче, но загружает весь массив. Второй - поточный, подходит для больших каталогов.

Проблема: В opendir() возможно false при ошибке. Решение: Проверять is_resource($dh).

Проблема: Некорректная обработка символических ссылок. Решение: Использовать is_link() для различения.

Случаи использования: Вывод списка файлов, сканирование каталога для обработки.

Как рекурсивно обойти все подкаталоги и файлы, используя современный ООП-подход?

Используйте RecursiveDirectoryIterator в сочетании с RecursiveIteratorIterator.


$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/path'),
    RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $item) {
    echo $item->getPathname() . "\n";
}
  

Пояснение: SELF_FIRST возвращает сначала каталог, затем его содержимое. Можно фильтровать с помощью RegexIterator.

Проблема: Слишком глубокое дерево может потребовать много памяти. Решение: Использовать FilesystemIterator с ограничением глубины.

Проблема: Ошибка при нечитаемых каталогах. Решение: Оборачивать в try-catch.

Случаи использования: Поиск файлов по расширению, резервное копирование, аудит.

Как найти все файлы, соответствующие шаблону (например, *.txt)?

Функция glob() выполняет поиск по шаблону.


$txtFiles = glob('/path/*.txt');
// результат: массив путей файлов
  

Пояснение: Поддерживает обычные шаблоны: *, ?, []. Для рекурсивного поиска используйте glob('/path/**/*.txt') (требуется PHP 5.3+ и флаг GLOB_BRACE).

Проблема: glob() не обрабатывает большие каталоги эффективно. Решение: Использовать итераторы.

Проблема: Не поддерживает регулярные выражения. Решение: Комбинировать с preg_grep().

Случаи использования: Фильтрация логов, обработка временных файлов.

Как создать временную директорию с уникальным именем и получить её путь?

Используйте sys_get_temp_dir() и tempnam() с последующим созданием каталога.


$tmpFile = tempnam(sys_get_temp_dir(), 'my_prefix_');
unlink($tmpFile); // удалить созданный файл
mkdir($tmpFile);  // создать каталог с тем же именем
  

Пояснение: tempnam() создаёт уникальное имя файла, но не каталога. Удаляем файл и создаём каталог. Альтернатива: uniqid() с ручным созданием.

Проблема: Гонка состояний между unlink и mkdir. Решение: Использовать mkdir() с флагом

@ и проверкой.

Случаи использования: Временное хранилище для загрузок, кэш.

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

Используйте stat(), disk_free_space().


$stat = stat('/path');
$freeSpace = disk_free_space('/path');
echo $stat['size']; // общий размер (не рекурсивно)
echo $freeSpace;    // свободное место на диске
  

Пояснение: stat() возвращает массив с информацией о файле/каталоге. Для рекурсивного размера требуется обход.

Проблема: stat() не показывает размер дерева. Решение: Написать рекурсивную функцию суммирования.

Случаи использования: Мониторинг дискового пространства, отчётность.

Как скопировать всю директорию с подкаталогами и файлами?

В PHP нет встроенной функции, требуется рекурсивное копирование.


function copyDir($src, $dst) {
    $dir = opendir($src);
    @mkdir($dst);
    while (($file = readdir($dir)) !== false) {
        if ($file != '.' && $file != '..') {
            $srcFile = $src . '/' . $file;
            $dstFile = $dst . '/' . $file;
            if (is_dir($srcFile)) {
                copyDir($srcFile, $dstFile);
            } else {
                copy($srcFile, $dstFile);
            }
        }
    }
    closedir($dir);
}
  

Пояснение: Рекурсивно создаёт целевые каталоги и копирует файлы.

Проблема: Перезапись существующих файлов. Решение: Добавить проверку file_exists().

Проблема: Символические ссылки копируются как файлы. Решение: Использовать is_link() и специальную обработку.

Случаи использования: Деплой, резервное копирование, дублирование структуры.

Как проверить, доступен ли каталог для записи или чтения?

Функции is_writable() и is_readable().


if (is_writable('/path')) {
    // можно создавать/удалять файлы
}
if (is_readable('/path')) {
    // можно читать содержимое
}
  

Пояснение: Проверяют не только права, но и фактическую возможность доступа.

Проблема: is_writable() может давать ложные результаты на некоторых системах. Решение: Дополнительная попытка создать/прочитать файл.

Случаи использования: Проверка перед операциями, конфигурация.

Расширенные примеры работы с директориями в PHP

Ниже приведены подробные примеры с выводом кода и результатами для наиболее сложных сценариев.

Пример

// Пример 1: Рекурсивное копирование каталога с сохранением прав
function copyDirRecursive($src, $dst) {
    if (!is_dir($src)) {
        throw new InvalidArgumentException("Source $src is not a directory");
    }
    if (!is_dir($dst)) {
        mkdir($dst, 0755, true);
    }
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($src, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    foreach ($iterator as $item) {
        $target = $dst . DIRECTORY_SEPARATOR . $iterator->getSubPathname();
        if ($item->isDir()) {
            mkdir($target, $item->getPerms());
        } else {
            copy($item->getPathname(), $target);
        }
    }
}

// Использование
copyDirRecursive('/tmp/source', '/tmp/destination');
echo "Копирование завершено";
Результат: Директория /tmp/destination создана с полной структурой, включая права доступа.
Пример

// Пример 2: Рекурсивное удаление с обработкой символических ссылок
function safeRemoveDir($path) {
    if (!file_exists($path)) return;
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    );
    foreach ($iterator as $item) {
        if ($item->isLink()) {
            unlink($item->getPathname());
        } elseif ($item->isDir()) {
            rmdir($item->getPathname());
        } else {
            unlink($item->getPathname());
        }
    }
    rmdir($path);
}

// Использование
safeRemoveDir('/tmp/unwanted');
echo "Каталог удалён";
Результат: Все файлы, подкаталоги и символические ссылки внутри /tmp/unwanted удалены, сама директория также удалена.
Пример

// Пример 3: Поиск всех файлов с определённым расширением (рекурсивно) с помощью итератора
$directory = new RecursiveDirectoryIterator('/var/www');
$filter = new RecursiveCallbackFilterIterator($directory, function($current, $key, $iterator) {
    // Оставляем только .php файлы
    if ($current->isFile() && $current->getExtension() === 'php') {
        return true;
    }
    // Для директорий продолжаем обход
    if ($current->isDir()) {
        return true;
    }
    return false;
});
$iterator = new RecursiveIteratorIterator($filter);

$phpFiles = [];
foreach ($iterator as $file) {
    $phpFiles[] = $file->getPathname();
}
echo "Найдено PHP-файлов: " . count($phpFiles);
Результат: Вывод количества PHP-файлов в /var/www и подкаталогах (например, 42).
Пример

// Пример 4: Получение размера каталога рекурсивно
function getDirSize($path) {
    $size = 0;
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS)
    );
    foreach ($iterator as $file) {
        if ($file->isFile()) {
            $size += $file->getSize();
        }
    }
    return $size;
}

$sizeBytes = getDirSize('/var/log');
echo "Размер каталога /var/log: " . round($sizeBytes / 1048576, 2) . " MB";
Результат: Размер каталога /var/log: 128.45 MB

Примечание:

Во всех примерах предполагается, что у скрипта есть соответствующие права доступа. Для работы с большими объёмами данных рекомендуется ограничивать глубину рекурсии или использовать поточную обработку.

Работа с директориями в PHP - comments

En
Dir php (php)