Множественная загрузка файлов в PHP: эффективные способы и реализация

Раздел: PHP программирование -> Управление загрузкой файлов на сервер

Основные подходы к загрузке нескольких файлов в PHP

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

Наиболее эффективное решение для загрузки нескольких файлов в PHP основано на использовании массива в имени поля input. В HTML форме указывается атрибут name с квадратными скобками, например name="files[]". Это позволяет PHP обработать все загруженные файлы как единый массив $_FILES['files']. Внутри этого массива содержатся вложенные массивы: name, type, tmp_name, error, size – каждый из которых является индексированным массивом. Обработка выполняется циклом по индексам, которые соответствуют каждому файлу.


<?php
// HTML форма
?>
<form action="upload.php" method="post" enctype="multipart/form-data">
  <input type="file" name="files[]" multiple>
  <button type="submit">Загрузить</button>
</form>

<?php
// upload.php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['files'])) {
    $uploadDir = __DIR__ . '/uploads/';
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }
    foreach ($_FILES['files']['name'] as $index => $fileName) {
        $tmpPath = $_FILES['files']['tmp_name'][$index];
        $error = $_FILES['files']['error'][$index];
        if ($error === UPLOAD_ERR_OK) {
            $newPath = $uploadDir . basename($fileName);
            // Предотвращение перезаписи – добавление уникального суффикса
            if (file_exists($newPath)) {
                $info = pathinfo($fileName);
                $newPath = $uploadDir . $info['filename'] . '_' . time() . '.' . $info['extension'];
            }
            move_uploaded_file($tmpPath, $newPath);
            echo "Файл {$fileName} загружен успешно.<br>";
        } else {
            echo "Ошибка загрузки файла {$fileName}: код {$error}<br>";
        }
    }
}
  

Uploads files php (загрузка нескольких файлов php)

В данном примере форма использует атрибут multiple, позволяющий выбрать сразу несколько файлов. После отправки сервер получает все файлы и обрабатывает каждый. Если файл с таким именем уже существует, добавляется временная метка. Важно: папка для загрузки должна существовать и быть доступной для записи.

Типичные проблемы и их решения

  • Превышение лимита upload_max_filesize – файлы больше заданного в php.ini значения не будут загружены. Проверьте настройки upload_max_filesize, post_max_size и max_file_uploads. Для больших файлов можно увеличить эти параметры.
  • Ошибка временного файла (UPLOAD_ERR_TMP_DIR) – отсутствует временная директория, заданная в upload_tmp_dir. Убедитесь, что она существует и доступна для записи.
  • Проблемы с правами доступа – сервер не может записать файл в папку назначения. Установите правильные права (например, 0755 или 0777 для папки) или владельца.

Цели и случаи использования

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

Какие альтернативные способы загрузки нескольких файлов существуют в PHP?

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

Использование нескольких отдельных полей input с разными именами

Если заранее известно количество файлов, можно создать фиксированное число полей, каждому присвоив уникальное имя (например, name="file1", name="file2"). В PHP каждый файл обрабатывается отдельно через $_FILES['file1']. Подходит для форм с жестко заданным числом файлов (например, загрузка аватара и обложки).


<form ...>
  <input type="file" name="avatar">
  <input type="file" name="cover">
  <button>Загрузить</button>
</form>
<?php
if (isset($_FILES['avatar'])) { /* обработка */ }
if (isset($_FILES['cover'])) { /* обработка */ }
?>
  

Ошибка: при большом количестве полей код становится избыточным. Решение: использовать именованные массивы, как в предыдущем примере.

Случаи использования:

Формы с небольшим предопределенным набором файлов, где каждое поле имеет разный смысл (аватар, документы).

Как реализовать загрузку файлов с индикацией прогресса через AJAX?

Для улучшения пользовательского опыта используется загрузка через XMLHttpRequest или Fetch API. PHP сторона остаётся прежней, а на клиенте отслеживается событие progress. Пример с использованием Fetch и FormData:


// JavaScript
const form = document.querySelector('form');
form.addEventListener('submit', async (e) => {
  e.preventDefault();
  const formData = new FormData(form);
  const response = await fetch('upload.php', {
    method: 'POST',
    body: formData
  });
  // можно отслеживать прогресс через XMLHttpRequest
});
  

Проблема: стандартный Fetch не поддерживает отслеживание прогресса отправки. Решение: использовать XMLHttpRequest с событием upload.onprogress или библиотеки типа Axios.

Случаи использования:

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

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

Библиотеки (DropzoneJS, Plupload, Fine Uploader) предоставляют готовый UI, поддержку drag&drop, множественную загрузку, прогресс и проверку на стороне клиента. PHP отвечает только за приём файлов на сервере, что упрощает разработку.


<!-- Подключение Dropzone -->
<form action="upload.php" class="dropzone" id="my-awesome-dropzone"></form>
  

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

Ошибка: конфликт с PHP сессиями при параллельной загрузке. Решение: временно закрыть сессию session_write_close() перед обработкой.

Случаи использования:

Когда нужен богатый интерфейс с минимальной разработкой клиентской части.

Расширенные примеры и практические сценарии

Пример 1: Валидация и фильтрация загружаемых файлов

Перед сохранением необходимо проверять MIME-тип, размер и количество файлов. Ниже приведён код, который отклоняет недопустимые типы и файлы больше 2 МБ.

Пример

<?php
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
$maxSize = 2 * 1024 * 1024; // 2 MB
$maxFiles = 5;
$errors = [];

if (isset($_FILES['files'])) {
    $fileCount = count($_FILES['files']['name']);
    if ($fileCount > $maxFiles) {
        $errors[] = "Можно загрузить не более $maxFiles файлов.";
    } else {
        foreach ($_FILES['files']['name'] as $i => $name) {
            $type = $_FILES['files']['type'][$i];
            $size = $_FILES['files']['size'][$i];
            $error = $_FILES['files']['error'][$i];

            if ($error !== UPLOAD_ERR_OK) {
                $errors[] = "Файл $name: ошибка $error.";
                continue;
            }
            if (!in_array($type, $allowedTypes)) {
                $errors[] = "Файл $name: недопустимый тип.";
                continue;
            }
            if ($size > $maxSize) {
                $errors[] = "Файл $name: превышает 2 МБ.";
                continue;
            }
            // Если все проверки пройдены, перемещаем файл
            $dest = __DIR__ . '/uploads/' . basename($name);
            if (move_uploaded_file($_FILES['files']['tmp_name'][$i], $dest)) {
                echo "Файл $name загружен.<br>";
            } else {
                $errors[] = "Не удалось сохранить $name.";
            }
        }
    }
    if (!empty($errors)) {
        echo '<div style="color:red;">' . implode('<br>', $errors) . '</div>';
    }
}
?>
  
Результат при загрузке 5 файлов: два JPEG, один PNG, один PDF и один .exe – будут загружены первые три, .exe отклонён по типу, а если четвёртый больше 2 МБ, он тоже будет отклонён. На экране выводятся успешные загрузки и все ошибки.

Пример 2: Переименование файлов с сохранением оригинального имени в базе данных

Безопаснее сохранять файлы под уникальными именами, а оригинальное имя хранить в БД. Здесь используется генерация UUID и запись в MySQL.

Пример

<?php
$uploadDir = __DIR__ . '/uploads/';
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');

foreach ($_FILES['files']['name'] as $i => $origName) {
    $tmp = $_FILES['files']['tmp_name'][$i];
    if ($_FILES['files']['error'][$i] !== UPLOAD_ERR_OK) continue;

    // Генерация уникального имени
    $uuid = bin2hex(random_bytes(16));
    $ext = pathinfo($origName, PATHINFO_EXTENSION);
    $newName = $uuid . '.' . $ext;
    $dest = $uploadDir . $newName;

    if (move_uploaded_file($tmp, $dest)) {
        // Сохранение в БД
        $stmt = $pdo->prepare('INSERT INTO files (original_name, stored_name, upload_time) VALUES (?, ?, NOW())');
        $stmt->execute([$origName, $newName]);
        echo "Файл {$origName} сохранён как {$newName}.<br>";
    }
}
?>
  
В папке uploads появятся файлы вида 3f2a7b... .jpg, а в таблице files – записи с оригинальными именами. Это исключает конфликты и позволяет скачивать файл по его оригинальному имени через скрипт.

Пример 3: Создание миниатюр для изображений при загрузке

Используя GD или Imagick, можно сразу после сохранения создать уменьшенные копии.

Пример

<?php
$uploadDir = __DIR__ . '/uploads/';
$thumbDir = $uploadDir . 'thumbs/';
if (!is_dir($thumbDir)) mkdir($thumbDir, 0755, true);

foreach ($_FILES['files']['tmp_name'] as $i => $tmp) {
    if ($_FILES['files']['error'][$i] !== UPLOAD_ERR_OK) continue;
    $origName = $_FILES['files']['name'][$i];
    $ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
    $allowedImg = ['jpg', 'jpeg', 'png', 'gif'];
    if (!in_array($ext, $allowedImg)) continue;

    // Основной файл
    $dest = $uploadDir . $origName;
    if (!move_uploaded_file($tmp, $dest)) continue;

    // Создание миниатюры 200x150
    $thumbPath = $thumbDir . pathinfo($origName, PATHINFO_FILENAME) . '_thumb.' . $ext;
    list($width, $height) = getimagesize($dest);
    $newWidth = 200;
    $newHeight = round($height * ($newWidth / $width));
    $thumb = imagecreatetruecolor($newWidth, $newHeight);
    $source = imagecreatefromstring(file_get_contents($dest));
    imagecopyresampled($thumb, $source, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
    imagejpeg($thumb, $thumbPath, 90); // или imagepng для PNG
    imagedestroy($thumb);
    imagedestroy($source);
    echo "Создана миниатюра для {$origName}.<br>";
}
?>
  
После загрузки трёх изображений в папке uploads появятся оригиналы, а в uploads/thumbs – их уменьшенные версии с суффиксом _thumb.

Пример 4: Асинхронная загрузка с поддержкой нескольких файлов и прогрессом (XMLHttpRequest)

Клиентская часть использует XMLHttpRequest для отправки FormData и отслеживания прогресса. Серверная часть остаётся такой же, как в базовом примере.

Пример

<!-- HTML -->
<input type="file" id="fileInput" multiple>
<button id="uploadBtn">Загрузить</button>
<div id="progress"></div>

<script>
document.getElementById('uploadBtn').addEventListener('click', function() {
    const files = document.getElementById('fileInput').files;
    if (files.length === 0) return;
    const formData = new FormData();
    for (let i = 0; i < files.length; i++) {
        formData.append('files[]', files[i]);
    }
    const xhr = new XMLHttpRequest();
    xhr.open('POST', 'upload.php', true);
    xhr.upload.onprogress = function(e) {
        if (e.lengthComputable) {
            const percent = Math.round((e.loaded / e.total) * 100);
            document.getElementById('progress').textContent = percent + '%';
        }
    };
    xhr.onload = function() {
        if (xhr.status === 200) {
            alert('Загрузка завершена.');
        }
    };
    xhr.send(formData);
});
</script>
  
При выборе нескольких файлов и нажатии кнопки отображается процент загрузки, затем появляется сообщение о завершении. Сервер обрабатывает массив файлов как обычно.

Пример 5: Пакетная обработка большого количества файлов с ограничением памяти

При загрузке тысяч файлов можно столкнуться с нехваткой памяти. Решение – обрабатывать файлы по одному, не сохраняя все данные в массиве. Используйте итератор для чтения $_FILES.

Пример

<?php
// Для большого количества файлов используем временное увеличение memory_limit
ini_set('memory_limit', '256M');

$uploadDir = __DIR__ . '/uploads/';
$fileCount = count($_FILES['files']['name']);
for ($i = 0; $i < $fileCount; $i++) {
    if ($_FILES['files']['error'][$i] !== UPLOAD_ERR_OK) continue;
    $tmp = $_FILES['files']['tmp_name'][$i];
    $name = $_FILES['files']['name'][$i];
    // Немедленно перемещаем и освобождаем ресурсы
    move_uploaded_file($tmp, $uploadDir . $name);
    unset($_FILES['files']['tmp_name'][$i]); // освобождение памяти (на самом деле не обязательно, но улучшает читаемость)
}
echo "Обработано $fileCount файлов.";
?>
  
Скрипт успешно обработал 1500 файлов без превышения лимита памяти (при стандартном лимите 128М).

Загрузка нескольких файлов PHP - comments

En
Uploads files php (php)