Прием файлов от пользователя в PHP проектах

Раздел: Серверное программирование -> Работа с файлами

Основной метод: формы и обработчик

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

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


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

<?php // upload.php
$targetDir = __DIR__ . '/uploads/';
if (!is_dir($targetDir)) {
    mkdir($targetDir, 0755, true);
}
$file = $_FILES['userfile'] ?? null;
if ($file && $file['error'] === UPLOAD_ERR_OK) {
    $tmpPath = $file['tmp_name'];
    $name = basename($file['name']);
    $destPath = $targetDir . $name;
    if (move_uploaded_file($tmpPath, $destPath)) {
        echo "Файл сохранен: " . htmlspecialchars($name);
    } else {
        echo "Ошибка перемещения файла.";
    }
} else {
    $errorCode = $file['error'] ?? UPLOAD_ERR_NO_FILE;
    echo "Ошибка загрузки: " . $errorCode;
}
?>
  

Index php path (пути файлов в php)

Пояснение: форма передает файл на сервер, PHP сохраняет его во временной директории. Скрипт проверяет наличие ошибки, используя константы UPLOAD_ERR_OK и другие. Затем перемещает файл в папку uploads. В реальных проектах необходимо дополнительно проверять размер и тип файла, а также генерировать уникальное имя для предотвращения перезаписи.

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

  • Файл не загружается, если превышен лимит upload_max_filesize в php.ini. Решение: увеличить лимит и/или обрабатывать ошибку UPLOAD_ERR_INI_SIZE.
  • Недостаточно прав на запись в папку назначения. Решение: проверить права (например, chmod 755).
  • Имена файлов могут содержать некорректные символы. Решение: использовать basename() и генерировать безопасное имя.
  • Файл может оказаться слишком большим для POST запроса из-за post_max_size. Решение: настроить параметры сервера.

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

Используйте атрибут multiple в теге input и добавьте квадратные скобки в имя поля (например, name="files[]"). На сервере массив $_FILES['files'] будет содержать информацию по каждому файлу.


<form action="upload_multi.php" method="post" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple>
    <input type="submit" value="Загрузить все">
</form>

<?php
$targetDir = __DIR__ . '/uploads/';
if (!is_dir($targetDir)) mkdir($targetDir, 0755, true);
if (!empty($_FILES['files']['name'][0])) {
    foreach ($_FILES['files']['name'] as $i => $name) {
        if ($_FILES['files']['error'][$i] === UPLOAD_ERR_OK) {
            $tmpPath = $_FILES['files']['tmp_name'][$i];
            $safeName = uniqid() . '-' . basename($name);
            move_uploaded_file($tmpPath, $targetDir . $safeName);
            echo "Сохранен: " . htmlspecialchars($safeName) . "<br>";
        }
    }
}
?>
  

Php tmp files (временные файлы php)

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

Как загружать файлы размером больше лимитов PHP?

Одно из решений - разбить файл на части на клиенте (JavaScript) и отправлять их последовательно или параллельно на сервер, где каждая часть дописывается к результирующему файлу. Это обходит ограничения upload_max_filesize и post_max_size. Ниже упрощенный серверный обработчик для приема чанков.


<?php // chunk_upload.php
$targetFile = __DIR__ . '/uploads/' . ($_POST['filename'] ?? 'chunk');
$chunkIndex = intval($_POST['chunk'] ?? 0);
$totalChunks = intval($_POST['chunks'] ?? 1);
$data = file_get_contents('php://input');
$mode = ($chunkIndex === 0) ? 'wb' : 'ab';
$fp = fopen($targetFile, $mode);
if ($fp) {
    fwrite($fp, $data);
    fclose($fp);
}
if ($chunkIndex === $totalChunks - 1) {
    echo "Загрузка завершена";
} else {
    echo "Чанк " . ($chunkIndex+1) . " из $totalChunks принят";
}
?>
  

Edit file php (редактирование php файла)

В реальном проекте необходимо добавить проверку токена, сессии, а также контрольную сумму для каждого чанка. На клиенте используется File.slice() и отправка через XHR.

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

Используйте объект FormData и XMLHttpRequest (или fetch). Серверный скрипт остается таким же, как при обычной загрузке. Клиентский код отправляет файл асинхронно.


<!-- HTML -->
<input type="file" id="fileInput">
<button onclick="upload()">Загрузить</button>
<div id="status"></div>

<script>
function upload() {
    var file = document.getElementById('fileInput').files[0];
    var formData = new FormData();
    formData.append('userfile', file);
    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'upload.php', true);
    xhr.onload = function() {
        if (xhr.status === 200) {
            document.getElementById('status').innerText = xhr.responseText;
        }
    };
    xhr.send(formData);
}
</script>
  

Php file get html (получение html файла через php)

Серверный скрипт (upload.php) может возвращать JSON с результатом. Для отслеживания прогресса используйте событие xhr.upload.onprogress.

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

Для надежной проверки типа изображения используйте функцию finfo или getimagesize. Создание миниатюр требует библиотеки GD или Imagick. Пример ниже использует GD для ресайза.


<?php
if ($_FILES['image']['error'] === UPLOAD_ERR_OK) {
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo, $_FILES['image']['tmp_name']);
    finfo_close($finfo);
    $allowed = ['image/jpeg', 'image/png', 'image/gif'];
    if (!in_array($mime, $allowed)) {
        die('Недопустимый тип файла');
    }
    // Создание миниатюры
    list($origW, $origH) = getimagesize($_FILES['image']['tmp_name']);
    $maxSize = 200;
    $ratio = $origW / $origH;
    if ($origW > $origH) {
        $newW = $maxSize;
        $newH = $maxSize / $ratio;
    } else {
        $newH = $maxSize;
        $newW = $maxSize * $ratio;
    }
    $thumb = imagecreatetruecolor($newW, $newH);
    $src = imagecreatefromstring(file_get_contents($_FILES['image']['tmp_name']));
    imagecopyresampled($thumb, $src, 0, 0, 0, 0, $newW, $newH, $origW, $origH);
    imagejpeg($thumb, __DIR__ . '/uploads/thumb_' . uniqid() . '.jpg', 80);
    imagedestroy($thumb);
    imagedestroy($src);
    echo 'Миниатюра создана';
}
?>
  

Root php (корневая директория сервера в php)

Важно: не полагаться только на расширение файла - mime-тип может быть подменен. Используйте проверку через содержимое.

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

Генерируйте уникальное имя, используя uniqid(), hash_file() или комбинацию с временной меткой. Удалите потенциально опасные символы из оригинального имени или полностью замените его.


<?php
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$safeName = md5(uniqid() . microtime()) . '.' . $ext;
// Либо
$hash = hash_file('sha256', $_FILES['file']['tmp_name']);
$safeName = $hash . '.' . $ext;
move_uploaded_file($_FILES['file']['tmp_name'], __DIR__ . '/uploads/' . $safeName);
?>
  

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

- Php open file (открытие файла в php)
- Ru reading php line (чтение строки из файла php)
- Php path (пути в php)

Расширенные сценарии загрузки

Загрузка через потоковый ввод (php://input)

Если файл передается в теле запроса (например, PUT запрос или application/octet-stream), можно прочитать данные напрямую из потока.

Пример

<?php
// put_upload.php - ожидает raw data в теле запроса
$filename = $_SERVER['HTTP_X_FILENAME'] ?? 'unknown.bin';
$input = fopen('php://input', 'rb');
$dest = fopen(__DIR__ . '/uploads/' . basename($filename), 'wb');
stream_copy_to_stream($input, $dest);
fclose($input);
fclose($dest);
echo "Файл $filename сохранен";
?>

Этот подход полезен для API, где клиент отправляет файл без формы.

Интеграция с облачным хранилищем (S3)

Используя библиотеку league/flysystem, можно абстрагироваться от локальной файловой системы. Пример сохранения файла в S3.

Пример

<?php
require 'vendor/autoload.php';
use League\Flysystem\Filesystem;
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
use Aws\S3\S3Client;

$client = new S3Client([
    'version' => 'latest',
    'region'  => 'us-east-1',
    'credentials' => [
        'key'    => 'YOUR_KEY',
        'secret' => 'YOUR_SECRET',
    ],
]);
$adapter = new AwsS3V3Adapter($client, 'bucket-name');
$filesystem = new Filesystem($adapter);

$file = $_FILES['userfile'];
if ($file['error'] === UPLOAD_ERR_OK) {
    $stream = fopen($file['tmp_name'], 'r');
    $filesystem->writeStream('uploads/' . $file['name'], $stream);
    fclose($stream);
    echo 'Файл загружен в S3';
}
?>

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

Сканирование загруженных файлов антивирусом ClamAV

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

Пример

<?php
if ($_FILES['file']['error'] === UPLOAD_ERR_OK) {
    $tmpPath = $_FILES['file']['tmp_name'];
    $clamSocket = fopen('unix:///var/run/clamav/clamd.sock', 'r+');
    if ($clamSocket) {
        fwrite($clamSocket, "SCAN $tmpPath\n");
        $response = fgets($clamSocket);
        fclose($clamSocket);
        if (strpos($response, 'OK') !== false) {
            move_uploaded_file($tmpPath, 'uploads/' . basename($_FILES['file']['name']));
            echo 'Файл безопасен и сохранен';
        } else {
            unlink($tmpPath);
            echo 'Файл содержит вирус: ' . htmlspecialchars($response);
        }
    }
}
?>
Пример ответа: "/tmp/phpXXXX: OK"

Важно: демон ClamAV должен быть установлен и запущен на сервере.

Загрузка CSV с последующей вставкой данных в MySQL

Пользователь загружает CSV файл, PHP читает его и добавляет строки в таблицу.

Пример

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
if ($_FILES['csv']['error'] === UPLOAD_ERR_OK) {
    $handle = fopen($_FILES['csv']['tmp_name'], 'r');
    $header = fgetcsv($handle); // первая строка - заголовки
    $stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
    while (($row = fgetcsv($handle)) !== false) {
        $stmt->execute([$row[0], $row[1]]);
    }
    fclose($handle);
    echo 'CSV импортирован, строк: ' . $stmt->rowCount();
}
?>
CSV импортирован, строк: 150

Рекомендуется проверять количество столбцов и экранировать данные.

Загрузка файлов в PHP - comments

En
Loading php (php)