Вывод списка файлов и папок на сервере средствами 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>';
?>