Работа с загружаемыми файлами в PHP через POST
Обработка 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.