Страница загрузок на PHP: от простого к сложному

Раздел: Разработка сайтов -> Файловый менеджмент

Реализация страницы загрузок на PHP

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

Наиболее распространённое решение включает HTML-форму с атрибутом enctype="multipart/form-data" и PHP-скрипт, который обрабатывает массив $_FILES. Основные шаги:

  • Убедиться, что форма отправлена методом POST.
  • Проверить, был ли выбран файл и произошла ли ошибка загрузки ($_FILES['file']['error'] === UPLOAD_ERR_OK).
  • Ограничить размер файла (например, проверкой значения $_FILES['file']['size']).
  • Проверить расширение файла (разрешить только определённые типы).
  • Переместить файл из временной директории в целевую с помощью move_uploaded_file().

<?php
// upload.php
$targetDir = "uploads/";
$maxFileSize = 2 * 1024 * 1024; // 2 MB
$allowedExtensions = ['jpg', 'png', 'pdf', 'zip'];

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
    $file = $_FILES['file'];
    if ($file['error'] !== UPLOAD_ERR_OK) {
        // обработка ошибки
        $errorMessage = 'Ошибка загрузки файла. Код: ' . $file['error'];
    } else {
        $originalName = basename($file['name']);
        $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        if (!in_array($ext, $allowedExtensions)) {
            $errorMessage = 'Недопустимое расширение файла.';
        } elseif ($file['size'] > $maxFileSize) {
            $errorMessage = 'Файл превышает максимальный размер.';
        } else {
            $destination = $targetDir . $originalName;
            if (file_exists($destination)) {
                $errorMessage = 'Файл с таким именем уже существует.';
            } else {
                if (move_uploaded_file($file['tmp_name'], $destination)) {
                    $successMessage = 'Файл успешно загружен: ' . $originalName;
                } else {
                    $errorMessage = 'Не удалось переместить загруженный файл.';
                }
            }
        }
    }
}
?>

Downloads php page (страница загрузок на php)

Типичные проблемы:

  • Ошибка UPLOAD_ERR_INI_SIZE / UPLOAD_ERR_FORM_SIZE – превышен лимит размера в php.ini или в форме. Решение: увеличить upload_max_filesize и post_max_size в php.ini или настройках хостинга.
  • Проблемы с правами доступа – папка назначения должна быть доступна для записи веб-сервером (обычно chmod 755 или 775).
  • Ложное расширение – злоумышленник может переименовать вредоносный файл в .jpg. Для повышения безопасности рекомендуется проверять MIME-тип через mime_content_type() или finfo.

Вариант 1: Загрузка нескольких файлов одновременно

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

Используйте массив в имени input: name="files[]" и атрибут multiple. В PHP массив $_FILES['files'] будет содержать вложенные массивы. Обработка ведётся в цикле.


<input type="file" name="files[]" multiple>
<?php
if (isset($_FILES['files'])) {
    $files = $_FILES['files'];
    $count = count($files['name']);
    for ($i = 0; $i < $count; $i++) {
        if ($files['error'][$i] === UPLOAD_ERR_OK) {
            $tmp = $files['tmp_name'][$i];
            $name = basename($files['name'][$i]);
            // проверки и move_uploaded_file
        }
    }
}
?>

Проблема: превышение лимита max_file_uploads в php.ini (по умолчанию 20). Решение: увеличить значение.

Вариант 2: Проверка MIME-типа для безопасности

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

Используйте расширение Fileinfo для проверки реального MIME-типа. Пример:


$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowedMimes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mime, $allowedMimes)) {
    // отклоняем файл
}

Ошибка: некоторые файлы (например, SVG) могут иметь текстовый MIME, что требует дополнительной логики.

Вариант 3: Переименование файла для избежания конфликтов

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

Генерируйте уникальное имя через uniqid() или md5(uniqid()).'.'.$ext. Либо добавляйте временную метку.


$newName = time() . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
$destination = $targetDir . $newName;

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

Вариант 4: Асинхронная загрузка через AJAX (без перезагрузки страницы)

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

На фронтенде используйте FormData и XMLHttpRequest (или fetch). PHP скрипт возвращает JSON-ответ.


// JavaScript
let formData = new FormData();
formData.append('file', fileInput.files[0]);
fetch('upload_ajax.php', {
    method: 'POST',
    body: formData
}).then(response => response.json())
  .then(data => console.log(data));

<?php
// upload_ajax.php
header('Content-Type: application/json');
// … проверки …
if (move_uploaded_file(...)) {
    echo json_encode(['success' => true, 'filename' => $newName]);
} else {
    echo json_encode(['success' => false, 'error' => 'Ошибка']);
}
?>

Проблема: CORS, если скрипт лежит на другом домене. Решение: установить заголовки Access-Control-Allow-Origin.

Вариант 5: Создание миниатюр для загруженных изображений

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

Используйте библиотеку GD или Imagick. Пример с GD:


$src = imagecreatefromjpeg($destination);
$thumbWidth = 200;
$thumbHeight = 150;
$thumb = imagecreatetruecolor($thumbWidth, $thumbHeight);
$origW = imagesx($src);
$origH = imagesy($src);
imagecopyresampled($thumb, $src, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $origW, $origH);
imagejpeg($thumb, $targetDir . 'thumb_' . $newName);
imagedestroy($src);
imagedestroy($thumb);

Ошибка: если изображение не JPEG, нужно использовать соответствующие функции (imagecreatefrompng, imagecreatefromgif).

Расширенные примеры реализации страницы загрузок

Пример 1: Загрузка с отображением прогресса (на основе сессии и JavaScript)

Для отслеживания прогресса загрузки большого файла можно использовать механизм uploadprogress (PECL) или более простой подход с опросом сервера через отдельный скрипт, который читает сессию. Однако популярный способ в современных проектах – использование библиотек на стороне клиента (например, Dropzone.js) с собственным PHP-обработчиком. Ниже показан минимальный вариант с периодическим опросом размера уже записанного файла (только для демонстрации концепции, не рекомендуется для production).

Пример

<!-- index.html -->
<input type="file" id="fileInput">
<progress id="progressBar" value="0" max="100"></progress>
<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
    var file = e.target.files[0];
    var formData = new FormData();
    formData.append('file', file);

    var xhr = new XMLHttpRequest();
    // Прогресс загрузки на сторону сервера (upload progress)
    xhr.upload.onprogress = function(e) {
        if (e.lengthComputable) {
            var percent = (e.loaded / e.total) * 100;
            document.getElementById('progressBar').value = percent;
        }
    };
    xhr.onload = function() {
        if (xhr.status === 200) {
            document.getElementById('progressBar').value = 100;
        }
    };
    xhr.open('POST', 'upload_progress.php', true);
    xhr.send(formData);
});
</script>
Пример

<?php
// upload_progress.php
session_start();
$target = 'uploads/' . uniqid() . '_' . basename($_FILES['file']['name']);
if (move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
    echo json_encode(['status' => 'ok', 'file' => $target]);
} else {
    echo json_encode(['status' => 'error']);
}
?>
Результат: прогресс-бар обновляется в реальном времени (зависит от браузера и сети).

Пример 2: Загрузка файла с записью информации в базу данных (MySQL)

Позволяет хранить метаданные о загруженных файлах: имя, размер, дата загрузки, MIME-тип. Сам файл обычно сохраняется в файловой системе, а в БД – только путь.

Пример

<?php
// upload_to_db.php (только пример)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
// ... проверки ...
$stmt = $pdo->prepare("INSERT INTO files (original_name, saved_name, size, mime, created_at) VALUES (?, ?, ?, ?, NOW())");
$stmt->execute([
    $originalName,
    $savedName,
    $file['size'],
    $mime
]);
// После записи в БД перемещаем файл в папку
move_uploaded_file($file['tmp_name'], 'uploads/' . $savedName);
?>
Результат: в таблице files появляется запись с ID, по которому можно ссылаться на файл.

Пример 3: Пакетная загрузка с очередью и последовательной обработкой

Когда необходимо загрузить много файлов и выполнить с ними дополнительные действия (например, конвертировать, создать несколько версий), удобно использовать очереди (RabbitMQ, Redis) или просто фоновые скрипты. Упрощённый вариант: все файлы сначала перемещаются во временную папку, а затем cron-запускает обработчик.

Пример

// В обработчике загрузки (upload_handler.php)
$tempDir = 'temp_uploads/';
foreach ($_FILES['files']['tmp_name'] as $i => $tmp) {
    $tempPath = $tempDir . uniqid() . '_' . $_FILES['files']['name'][$i];
    move_uploaded_file($tmp, $tempPath);
    // Добавляем задачу в очередь (например, файл-лист)
    file_put_contents('queue.txt', $tempPath . PHP_EOL, FILE_APPEND);
}
// Обработчик (process_queue.php), запускаемый по расписанию:
$queue = file('queue.txt', FILE_IGNORE_NEW_LINES);
foreach ($queue as $path) {
    if (file_exists($path)) {
        // Выполнить обработку (создать миниатюры, проверить вирусы и т.д.)
        // Затем переместить в конечную директорию
        rename($path, 'uploads/' . basename($path));
    }
}
file_put_contents('queue.txt', ''); // очистить
Результат: большой объём файлов не блокирует работу пользователя, обработка происходит в фоне.

Страница загрузок на PHP - comments

En
Downloads php page (php)