Как обработать загрузку файлов через форму?php-36

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

1. HTML-форма для загрузки

<form method="post" enctype="multipart/form-data" action="upload.php">
    <input type="file" name="userfile" accept=".jpg,.jpeg,.png,.pdf,.docx" multiple>
    <input type="submit" value="Загрузить">
</form>

Ключевые атрибуты:

  • enctype="multipart/form-data" - обязателен для загрузки файлов
  • accept - ограничение типов файлов (не заменяет серверную проверку)
  • multiple - разрешает выбор нескольких файлов

2. Базовый обработчик

<?php
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
const UPLOAD_DIR = __DIR__ . '/uploads/';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    exit('Метод не разрешен');
}

if (!isset($_FILES['userfile']) {
    http_response_code(400);
    exit('Файл не был отправлен');
}

// Создаем директорию, если не существует
if (!file_exists(UPLOAD_DIR) && !mkdir(UPLOAD_DIR, 0755, true)) {
    http_response_code(500);
    exit('Не удалось создать директорию для загрузки');
}

$files = is_array($_FILES['userfile']['name'])
    ? reorganizeFiles($_FILES['userfile'])
    : [$_FILES['userfile']];

foreach ($files as $file) {
    processUpload($file);
}

echo 'Файлы успешно загружены';

function processUpload(array $file): void {
    // Проверка ошибок загрузки
    if ($file['error'] !== UPLOAD_ERR_OK) {
        throw new RuntimeException(handleUploadError($file['error']));
    }

    // Проверка типа файла
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($file['tmp_name']);

    if (!in_array($mime, ALLOWED_TYPES)) {
        throw new RuntimeException("Недопустимый тип файла: {$mime}");
    }

    // Проверка размера
    if ($file['size'] > MAX_SIZE) {
        throw new RuntimeException("Превышен максимальный размер файла");
    }

    // Генерация безопасного имени
    $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
    $basename = bin2hex(random_bytes(8));
    $filename = sprintf('%s.%s', $basename, $extension);
    $destination = UPLOAD_DIR . $filename;

    // Перемещение файла
    if (!move_uploaded_file($file['tmp_name'], $destination)) {
        throw new RuntimeException("Не удалось сохранить файл");
    }

    // Установка прав
    chmod($destination, 0644);
}

function reorganizeFiles(array $files): array {
    $result = [];
    foreach ($files as $key => $values) {
        foreach ($values as $index => $value) {
            $result[$index][$key] = $value;
        }
    }
    return $result;
}

function handleUploadError(int $error): string {
    switch ($error) {
        case UPLOAD_ERR_INI_SIZE:
        case UPLOAD_ERR_FORM_SIZE:
            return 'Превышен максимальный размер файла';
        case UPLOAD_ERR_PARTIAL:
            return 'Файл загружен частично';
        case UPLOAD_ERR_NO_FILE:
            return 'Файл не был загружен';
        case UPLOAD_ERR_NO_TMP_DIR:
            return 'Отсутствует временная директория';
        case UPLOAD_ERR_CANT_WRITE:
            return 'Не удалось записать файл на диск';
        case UPLOAD_ERR_EXTENSION:
            return 'Расширение PHP остановило загрузку';
        default:
            return 'Неизвестная ошибка загрузки';
    }
}

3. Ключевые моменты безопасности

  1. Проверка метода запроса: Только POST
  2. Валидация MIME-типа: Используем finfo, а не доверяем данным клиента
  3. Генерация имени файла: Случайное имя для предотвращения перезаписи
  4. Проверка размера: На сервере и в PHP-конфигурации
  5. Права доступа: 0644 для файлов, 0755 для директорий
  6. Обработка ошибок: Все возможные коды ошибок UPLOAD_ERR_*

4. Настройки php.ini для загрузки

file_uploads = On
upload_max_filesize = 10M
post_max_size = 12M
max_file_uploads = 20
upload_tmp_dir = /tmp

5. Дополнительные улучшения

  1. Логирование загрузок:
$logEntry = sprintf(
    "[%s] %s: %s (%s)\n",
    date('Y-m-d H:i:s'),
    $filename,
    $mime,
    $file['size']
);
file_put_contents(UPLOAD_DIR . 'upload.log', $logEntry, FILE_APPEND);
  1. Ресайз изображений (если нужно):
if (str_starts_with($mime, 'image/')) {
    $image = new Imagick($destination);
    $image->resizeImage(800, 600, Imagick::FILTER_LANCZOS, 1, true);
    $image->writeImage($destination);
}
  1. Проверка на вирусы:
$clamav = new ClamAV();
if (!$clamav->scan($destination)) {
    unlink($destination);
    throw new RuntimeException("Файл содержит вредоносный код");
}

Резюмируем:

  • Всегда проверяйте enctype="multipart/form-data" в форме
  • Используйте move_uploaded_file() вместо copy()/rename()
  • Валидируйте тип, размер и содержимое файла
  • Генерируйте уникальные имена для предотвращения коллизий
  • Настройте соответствующие лимиты в php.ini
  • Реализуйте обработку множественных файлов через реорганизацию массива $_FILES