Работа с загружаемыми файлами в PHP через POST

Раздел: Разработка на PHP -> Обработка запросов

Обработка POST запросов с файлами в PHP

Основной и наиболее эффективный способ приёма файлов через POST

Когда клиент отправляет файл на сервер, браузер использует multipart/form-data кодировку. PHP автоматически помещает информацию о файле в суперглобальный массив $_FILES. Для перемещения загруженного файла в постоянное хранилище применяется функция move_uploaded_file().

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


<!-- Форма HTML -->
<form action="upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="myfile">
    <input type="submit" value="Загрузить">
</form>

<?php
// upload.php
$targetDir = __DIR__ . '/uploads/';
$targetFile = $targetDir . basename($_FILES['myfile']['name']);
$uploadOk = 1;

// Проверка на ошибки
if ($_FILES['myfile']['error'] !== UPLOAD_ERR_OK) {
    echo 'Ошибка загрузки';
    $uploadOk = 0;
}

if ($uploadOk && move_uploaded_file($_FILES['myfile']['tmp_name'], $targetFile)) {
    echo 'Файл сохранён: ' . htmlspecialchars($targetFile);
} else {
    echo 'Не удалось сохранить файл';
}
?>

Пояснение: форма обязана содержать атрибут enctype="multipart/form-data", иначе файл не будет передан. Проверка кода ошибки позволяет отловить проблемы (превышение размера, частичная загрузка и т.д.). Функция move_uploaded_file() безопаснее, чем copy(), так как проверяет, что файл действительно был загружен через HTTP POST.

Типичные ошибки и их решение

  • $_FILES пуст – вероятно, забыт enctype в форме или превышен лимит post_max_size.
  • Ошибка UPLOAD_ERR_INI_SIZE (1) – файл больше значения upload_max_filesize в php.ini.
  • Ошибка UPLOAD_ERR_NO_TMP_DIR (6) – отсутствует временная папка. Проверить настройки upload_tmp_dir.
  • move_uploaded_file не срабатывает – обычно из-за отсутствия прав на запись в целевую директорию.

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

Имя поля ввода задаётся как массив: name="files[]". В PHP данные приходят в виде массива $_FILES['files']['name'] и т.д.


<form action="upload_multi.php" method="post" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple>
    <input type="submit">
</form>

<?php
$targetDir = 'uploads/';
foreach ($_FILES['files']['name'] as $index => $name) {
    if ($_FILES['files']['error'][$index] === UPLOAD_ERR_OK) {
        $tmp = $_FILES['files']['tmp_name'][$index];
        $dest = $targetDir . basename($name);
        move_uploaded_file($tmp, $dest);
    }
}
?>

Проблема: атрибут multiple не поддерживается в старых браузерах. Альтернатива – несколько полей с разными именами.

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

Не полагайтесь только на расширение. Используйте mime_content_type() или finfo_file() для определения реального MIME-типа.


<?php
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['myfile']['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowedTypes)) {
    die('Недопустимый тип файла: ' . $mime);
}
?>

MIME-тип может быть подменён, но finfo анализирует содержимое файла, что сложнее обойти. Дополнительно проверьте расширение и используйте белый список.

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

Помимо upload_max_filesize и post_max_size, можно проверять размер в коде:


$maxSize = 2 * 1024 * 1024; // 2 MB
if ($_FILES['myfile']['size'] > $maxSize) {
    die('Файл слишком большой');
}
?>

Если размер превышает upload_max_filesize, то $_FILES всё равно будет содержать файл с ошибкой UPLOAD_ERR_INI_SIZE. Поэтому проверка после загрузки – второй рубеж.

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

Генерируйте уникальное имя, например, с помощью uniqid() или md5(time()).


$ext = pathinfo($_FILES['myfile']['name'], PATHINFO_EXTENSION);
$newName = uniqid('file_', true) . '.' . $ext;
$dest = 'uploads/' . $newName;
move_uploaded_file($_FILES['myfile']['tmp_name'], $dest);
?>

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

Загрузка изображений с созданием миниатюры (GD)

Пример

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) {
    $tmp = $_FILES['image']['tmp_name'];
    if ($_FILES['image']['error'] === UPLOAD_ERR_OK && getimagesize($tmp)) {
        $src = imagecreatefromstring(file_get_contents($tmp));
        $width = imagesx($src);
        $height = imagesy($src);
        $newWidth = 150;
        $newHeight = intval($height * $newWidth / $width);
        $thumb = imagecreatetruecolor($newWidth, $newHeight);
        imagecopyresampled($thumb, $src, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
        imagepng($thumb, 'thumbs/' . uniqid() . '.png');
        imagedestroy($thumb);
        imagedestroy($src);
        echo 'Миниатюра создана';
    }
}
?>
Результат: в папке thumbs появляется PNG изображение шириной 150 пикселей.

Асинхронная загрузка через AJAX (XMLHttpRequest и FormData)

Пример

// JavaScript
let formData = new FormData();
formData.append('file', document.getElementById('fileInput').files[0]);

fetch('upload.php', {
    method: 'POST',
    body: formData
}).then(response => response.text()).then(console.log);
Пример

<?php
// upload.php (тот же, что в базовом примере)
?>

При использовании FormData не нужно указывать Content-Type, браузер устанавливает multipart/form-data автоматически. На сервере данные приходят в $_FILES как обычно.

Разбиение больших файлов на части (chunk upload)

Пример

// Идея: клиент режет файл на куски (Blob.slice), каждый кусок отправляется POST-запросом с номером части.
// Сервер собирает части в один файл.
if (isset($_FILES['chunk'])) {
    $chunkIndex = intval($_POST['chunkIndex']);
    $totalChunks = intval($_POST['totalChunks']);
    $filename = $_POST['filename'];
    $tmpPath = 'chunks/' . $filename . '.part' . $chunkIndex;
    move_uploaded_file($_FILES['chunk']['tmp_name'], $tmpPath);

    if ($chunkIndex === $totalChunks - 1) {
        $final = fopen('uploads/' . $filename, 'wb');
        for ($i = 0; $i < $totalChunks; $i++) {
            $part = fopen('chunks/' . $filename . '.part' . $i, 'rb');
            stream_copy_to_stream($part, $final);
            fclose($part);
            unlink('chunks/' . $filename . '.part' . $i);
        }
        fclose($final);
    }
}
?>
Результат: большие файлы загружаются порциями, обходя ограничение upload_max_filesize (если настроено на уровне сервера).

Приём файлов через PSR-7 (на примере Slim Framework)

Пример

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;

$app->post('/upload', function (ServerRequestInterface $request, ResponseInterface $response) {
    $uploadedFiles = $request->getUploadedFiles();
    /** @var UploadedFileInterface $uploadedFile */
    $uploadedFile = $uploadedFiles['file'];
    $uploadedFile->moveTo('uploads/' . $uploadedFile->getClientFilename());
    return $response->withStatus(200);
});
?>

PSR-7 абстрагирует суперглобальные массивы, делая код переносимым между фреймворками.

Проверка загрузки через cURL (имитация клиента)

Пример

// Пример отправки файла через cURL в PHP (клиентская часть)
$ch = curl_init('http://example.com/upload.php');
curl_setopt($ch, CURLOPT_POST, true);
$cfile = new CURLFile('/path/to/local/file.jpg', 'image/jpeg', 'file.jpg');
curl_setopt($ch, CURLOPT_POSTFIELDS, ['file' => $cfile]);
curl_exec($ch);
curl_close($ch);
?>
Результат: файл отправляется на сервер, где обрабатывается стандартным $_FILES.

Обработка POST-запросов с файлами в PHP - comments

En
Php post файл (php)