Вывод списка файлов и папок на сервере средствами PHP

Раздел: Веб-разработка на PHP -> Работа с данными в PHP

Основные методы листинга директорий в PHP

Наиболее эффективное и простое решение для получения списка файлов и папок в заданном каталоге встроенная функция scandir(). Она возвращает массив всех элементов директории, включая служебные записи «.» и «..». Фильтрация и сортировка возлагаются на разработчика.


<?php
$path = __DIR__ . '/uploads';
$items = scandir($path);
foreach ($items as $item) {
    if ($item === '.' || $item === '..') continue;
    $fullPath = $path . '/' . $item;
    if (is_file($fullPath)) {
        echo 'Файл: ' . $item . "\n";
    } elseif (is_dir($fullPath)) {
        echo 'Папка: ' . $item . "\n";
    }
}
?>

Функция scandir() работает быстро, но не позволяет обрабатывать исключения при отсутствии прав доступа. Для гарантированной проверки существования директории используется is_dir() перед вызовом.

Возможная проблема:

При отсутствии прав на чтение каталога scandir() возвращает false и генерирует ошибку уровня E_WARNING. Решение: подавить вывод ошибок оператором @ (не рекомендуется) или проверять результат с помощью is_dir() и is_readable().

Как отобразить только файлы определённого расширения?

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


<?php
$path = __DIR__ . '/images';
// все файлы .jpg
$images = glob($path . '/*.jpg');
foreach ($images as $img) {
    echo basename($img) . "\n";
}
?>

Типичная ошибка:

Шаблон в glob() включает путь, поэтому возвращаются полные пути. Если нужно только имя, применяют basename(). Кроме того, glob не видит скрытые файлы (начинающиеся с точки) по умолчанию.

Как рекурсивно обойти все подкаталоги?

Для обхода вложенных папок используется класс RecursiveDirectoryIterator в связке с RecursiveIteratorIterator. Это объектно-ориентированный подход, дающий полный контроль над рекурсией.


<?php
$dir = new RecursiveDirectoryIterator(__DIR__ . '/projects');
$iterator = new RecursiveIteratorIterator($dir);
foreach ($iterator as $fileinfo) {
    if ($fileinfo->isFile()) {
        echo $fileinfo->getPathname() . "\n";
    }
}
?>

По умолчанию итератор пропускает «.» и «..». Для включения скрытых файлов используется флаг FilesystemIterator::SKIP_DOTS.

Возможная проблема:

Большая глубина рекурсии или множество файлов могут привести к переполнению памяти. Решение: ограничение глубины или использование постраничного вывода с помощью LimitIterator.

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

Функция scandir() предоставляет второй параметр для порядка сортировки (SCANDIR_SORT_ASCENDING, SCANDIR_SORT_DESCENDING, SCANDIR_SORT_NONE), но сортировка ведётся по имени, не по дате. Для сортировки по дате приходится собирать массив с метаданными и сортировать вручную.


<?php
$path = __DIR__ . '/documents';
$items = array_diff(scandir($path), array('.', '..'));
$files = [];
foreach ($items as $item) {
    $full = $path . '/' . $item;
    $files[] = [
        'name' => $item,
        'time' => filemtime($full)
    ];
}
usort($files, function($a, $b) {
    return $b['time'] - $a['time']; // по убыванию
});
foreach ($files as $f) {
    echo $f['name'] . ' - ' . date('Y-m-d H:i:s', $f['time']) . "\n";
}
?>

Такой подход гарантирует контроль над порядком вывода, но требует дополнительных затрат времени на чтение атрибутов.

Как вывести листинг в виде HTML-таблицы с иконками?

Для удобства веб-интерфейса результаты оборачиваются в HTML. В примере используется DirectoryIterator для получения информации о каждом элементе.


<?php
$path = __DIR__ . '/public';
$dir = new DirectoryIterator($path);
echo '<table><tr><th>Имя</th><th>Тип</th><th>Размер</th></tr>';
foreach ($dir as $item) {
    if ($item->isDot()) continue;
    $type = $item->isDir() ? 'Папка' : 'Файл';
    $size = $item->isFile() ? $item->getSize() . ' байт' : '-';
    echo '<tr><td>' . $item->getFilename() . '</td><td>' . $type . '</td><td>' . $size . '</td></tr>';
}
echo '</table>';
?>

Типичная ошибка:

При выводе в HTML нужно экранировать спецсимволы функцией htmlspecialchars(), особенно если имена файлов содержат < > &. В примере это опущено для краткости, в реальном коде обязательно применять.

Расширенные примеры файлового листинга

Листинг с группировкой по расширениям с total-статистикой

Пример

<?php
$path = __DIR__ . '/data';
$items = scandir($path);
$groups = [];
foreach ($items as $item) {
    if ($item === '.' || $item === '..') continue;
    $full = $path . '/' . $item;
    if (!is_file($full)) continue;
    $ext = pathinfo($item, PATHINFO_EXTENSION);
    $groups[$ext][] = $item;
}
echo "Статистика по типам файлов:\n";
foreach ($groups as $ext => $files) {
    echo "Расширение .$ext: " . count($files) . " файлов\n";
    foreach ($files as $f) {
        echo "  - $f\n";
    }
}
?>
Статистика по типам файлов:
Расширение .txt: 3 файлов
  - notes.txt
  - list.txt
  - readme.txt
Расширение .jpg: 2 файлов
  - photo1.jpg
  - photo2.jpg

Фильтрация по диапазону дат (от и до) с использованием DirectoryIterator

Пример

<?php
$path = __DIR__ . '/logs';
$start = strtotime('2025-01-01');
$end = strtotime('2025-01-31');
$dir = new DirectoryIterator($path);
$filtered = [];
foreach ($dir as $item) {
    if ($item->isFile()) {
        $mtime = $item->getMTime();
        if ($mtime >= $start && $mtime <= $end) {
            $filtered[] = $item->getFilename();
        }
    }
}
echo "Файлы, изменённые в январе 2025:\n";
print_r($filtered);
?>

Генерация JSON-списка для AJAX-подгрузки

Пример

<?php
header('Content-Type: application/json; charset=utf-8');
$path = __DIR__ . '/documents';
$dir = new DirectoryIterator($path);
$result = [];
foreach ($dir as $item) {
    if ($item->isDot()) continue;
    $result[] = [
        'name' => $item->getFilename(),
        'is_dir' => $item->isDir(),
        'size' => $item->isFile() ? $item->getSize() : null,
        'modified' => date('c', $item->getMTime())
    ];
}
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>
[
    {
        "name": "report.pdf",
        "is_dir": false,
        "size": 245760,
        "modified": "2025-03-10T14:30:00+03:00"
    },
    {
        "name": "archive",
        "is_dir": true,
        "size": null,
        "modified": "2025-03-09T09:15:00+03:00"
    }
]

Обработка очень большой директории с использованием LimitIterator

Пример

<?php
$path = __DIR__ . '/bigdata';
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 50;
$offset = ($page - 1) * $perPage;
$dir = new DirectoryIterator($path);
$limited = new LimitIterator($dir, $offset, $perPage);
echo "Страница $page (первые $perPage элементов):\n";
foreach ($limited as $item) {
    if ($item->isDot()) continue;
    echo $item->getFilename() . "\n";
}
?>

Безопасный листинг с экранированием и проверкой прав

Пример

<?php
$path = __DIR__ . '/protected';
if (!is_dir($path) || !is_readable($path)) {
    echo 'Директория недоступна для чтения';
    exit;
}
$items = @scandir($path);
if ($items === false) {
    echo 'Не удалось прочитать содержимое';
    exit;
}
echo '<ul>';
foreach ($items as $item) {
    if ($item[0] === '.') continue; // пропускаем скрытые
    echo '<li>' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '</li>';
}
echo '</ul>';
?>

Листинг PHP - comments

En
Listing php (php)