Глиф Учебники

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

Справочник · PHP · 2 минуты чтения

Форма для загрузки обязательно отправляется методом POST и с атрибутом enctype="multipart/form-data" — без него браузер пришлёт только имя файла, а не сам файл. Файл upload.php с формой и обработкой:

PHP
<?php
$message = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $file = $_FILES['photo'] ?? null;
    $allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
    $maxSize = 2 * 1024 * 1024;

    if (!isset($file['error']) || $file['error'] !== UPLOAD_ERR_OK) {
        $message = 'Файл не загружен';
    } elseif ($file['size'] > $maxSize) {
        $message = 'Файл больше 2 МБ';
    } else {
        $type = (new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
        if (!isset($allowed[$type])) {
            $message = 'Можно загружать только JPEG, PNG и WebP';
        } else {
            $name = bin2hex(random_bytes(16)) . '.' . $allowed[$type];
            if (move_uploaded_file($file['tmp_name'], __DIR__ . '/uploads/' . $name)) {
                $message = 'Файл сохранён как ' . $name;
            } else {
                $message = 'Не удалось сохранить файл';
            }
        }
    }
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="utf-8">
    <title>Загрузка фото</title>
</head>
<body>
<?php if ($message !== ''): ?>
    <p><?= htmlspecialchars($message) ?></p>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
    <input type="file" name="photo" accept="image/jpeg,image/png,image/webp">
    <button>Загрузить</button>
</form>
</body>
</html>

Рядом с upload.php создайте папку uploads, и у веб-сервера должно быть право писать в неё (урок Файлы и подключение).

Что проверяется и почему

Загруженный файл PHP кладёт во временную папку, а сведения о нём — в $_FILES['photo']: name (имя на компьютере посетителя), type (тип, который назвал браузер), size (размер в байтах), tmp_name (путь к временному файлу), error (код ошибки). Временный файл удаляется в конце запроса, поэтому его нужно перенести функцией move_uploaded_file.

  1. Код ошибки. UPLOAD_ERR_OK (0) — файл пришёл целиком. Остальные коды: UPLOAD_ERR_INI_SIZE — больше лимита php.ini, UPLOAD_ERR_PARTIAL — загрузился частично, UPLOAD_ERR_NO_FILE — файл не выбран.
  2. Размер — свой предел, даже если сервер разрешает больше.
  3. Настоящий тип. Расширение в имени и поле type задаёт посетитель — им верить нельзя. finfo смотрит на содержимое файла. Нужно расширение fileinfo: в Ubuntu оно есть сразу, в Windows включается строкой extension=fileinfo в php.ini.
  4. Новое имя. Имя посетителя не используется вовсе: в нём могут быть ../, русские буквы, пробелы или расширение .php. random_bytes даёт случайное имя, а расширение берётся из списка разрешённых типов.

Главная опасность загрузки — файл с кодом. Если на сервер попадёт shell.php и его можно открыть по адресу, злоумышленник выполнит на сервере любые команды. Поэтому: только типы из короткого списка, имя и расширение — свои, папку загрузок лучше держать вне корня сайта (папки, которую веб-сервер отдаёт по адресам) или запретить в ней выполнение PHP настройкой веб-сервера.

Лимиты в php.ini

Настройки
upload_max_filesize = 2M
post_max_size = 8M
max_file_uploads = 20

Это значения по умолчанию. upload_max_filesize — предел одного файла, post_max_size — всего запроса вместе с полями, он должен быть больше. Если запрос превысил post_max_size, PHP не заполнит ни $_POST, ни $_FILES: массивы окажутся пустыми, как будто форму не отправляли. После правки php.ini перезапустите веб-сервер или PHP-FPM.

Если не работает

Загруженные картинки выводят по сохранённому имени: <img src="/uploads/<?= htmlspecialchars($name) ?>">, если папка доступна из браузера.