W3docs

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

Узнайте, как реализовать загрузку файлов в PHP с помощью $_FILES и move_uploaded_file() — с валидацией и настройками безопасности.

Загрузка файлов — распространённое требование в веб-разработке. Будь то фотография профиля, PDF-документ или CSV-импорт, возможность отправлять файлы на сервер является ключевой функцией большинства приложений. В PHP это реализуется с помощью функции move_uploaded_file() и суперглобального массива $_FILES.

В этой главе рассматривается полный процесс загрузки: настройка HTML-формы, чтение метаданных загруженного файла из $_FILES, его безопасная валидация и перемещение в конечное место. Также описываются коды ошибок загрузки и настройки конфигурации PHP, управляющие ограничениями на загрузку.

Как работает загрузка файлов в PHP

Загрузка файла происходит в три этапа:

  1. Браузер отправляет файл в POST-запросе с типом multipart/form-data.
  2. PHP получает файл и записывает его во временное место на диске, после чего предоставляет информацию о нём через массив $_FILES.
  3. Ваш скрипт проверяет файл и перемещает его из временного места в постоянное с помощью move_uploaded_file().

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

Суперглобальный массив $_FILES

При загрузке файла информация о нём сохраняется в суперглобальном массиве $_FILES. Для поля формы с именем userfile массив содержит следующие ключи:

  • $_FILES['userfile']['name'] — оригинальное имя загруженного файла.
  • $_FILES['userfile']['type'] — MIME-тип загруженного файла.
  • $_FILES['userfile']['size'] — размер загруженного файла в байтах.
  • $_FILES['userfile']['tmp_name'] — временное расположение загруженного файла на сервере.
  • $_FILES['userfile']['error'] — код ошибки, указывающий на наличие проблем при загрузке файла (см. коды ошибок ниже).

Шаг 1: HTML-форма

Атрибут enctype формы обязательно должен быть установлен в multipart/form-data, а метод обязательно должен быть POST. Без multipart/form-data браузер отправит только имя файла, но не его содержимое, и массив $_FILES окажется пустым.

<form action="upload.php" method="POST" enctype="multipart/form-data">
  <input type="file" name="userfile">
  <input type="submit" value="Upload">
</form>

Шаг 2: Валидация загрузки

Никогда не доверяйте загруженному файлу. Перед его перемещением проверьте три вещи: что загрузка прошла успешно, что размер файла в пределах допустимого, и что файл действительно того типа, который вы ожидаете.

Проверка кода ошибки

Всегда проверяйте $_FILES['userfile']['error'] первым делом. PHP определяет именованные константы для возможных значений:

КонстантаЗначениеОписание
UPLOAD_ERR_OK0Ошибок нет, файл загружен успешно.
UPLOAD_ERR_INI_SIZE1Файл превышает upload_max_filesize в php.ini.
UPLOAD_ERR_FORM_SIZE2Файл превышает значение поля MAX_FILE_SIZE в форме.
UPLOAD_ERR_PARTIAL3Файл был загружен лишь частично.
UPLOAD_ERR_NO_FILE4Файл не был загружен.
UPLOAD_ERR_NO_TMP_DIR6Отсутствует временная папка.
UPLOAD_ERR_CANT_WRITE7Не удалось записать файл на диск.

Безопасная проверка типа и размера

Не полагайтесь на $_FILES['userfile']['type']. Это значение предоставляется браузером и может быть легко подделано злоумышленником. Вместо этого определяйте реальный MIME-тип по содержимому файла с помощью расширения finfo и самостоятельно ограничивайте размер:

$file = $_FILES['userfile'];

// 1. Did the upload succeed?
if ($file['error'] !== UPLOAD_ERR_OK) {
  exit("Upload failed with error code " . $file['error']);
}

// 2. Enforce a maximum size (2 MB here).
$maxBytes = 2 * 1024 * 1024;
if ($file['size'] > $maxBytes) {
  exit("File is too large.");
}

// 3. Detect the real MIME type, not the client-supplied one.
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime  = $finfo->file($file['tmp_name']);

$allowed = [
  'image/jpeg' => 'jpg',
  'image/png'  => 'png',
  'image/gif'  => 'gif',
];

if (!isset($allowed[$mime])) {
  exit("Only JPEG, PNG, and GIF images are allowed.");
}

Шаг 3: Перемещение файла в конечное место

После того как файл прошёл валидацию, переместите его с помощью move_uploaded_file(). Функция принимает два аргумента: временный путь ($_FILES['userfile']['tmp_name']) и целевой путь. Важно использовать именно эту функцию, а не copy() или rename() — она проверяет, что файл был получен через настоящую HTTP-загрузку, что защищает от атак, в которых злоумышленник пытается заставить ваш скрипт переместить произвольный файл на сервере.

Генерируйте конечное имя файла самостоятельно, не доверяя оригинальному имени. Это защищает от атак с обходом каталогов (например, имя вида ../../config.php) и перезаписи существующих файлов:

$targetDir = "uploads/";

// Build a safe, unique file name; never trust the client's name.
$extension  = $allowed[$mime];
$safeName   = bin2hex(random_bytes(8)) . "." . $extension;
$targetFile = $targetDir . $safeName;

if (move_uploaded_file($file['tmp_name'], $targetFile)) {
  echo "The file was uploaded as " . $safeName;
} else {
  echo "There was an error saving the file.";
}

Для получения подробной информации об этой функции и сопутствующей is_uploaded_file() см. справочные главы move_uploaded_file() и is_uploaded_file().

Конфигурация, влияющая на загрузку файлов

Несколько директив php.ini незаметно ограничивают то, что ваш скрипт может принять. Если загрузка больших файлов завершается ошибкой, несмотря на правильный код, проверьте следующие настройки:

  • file_uploads — должно быть On, чтобы загрузка файлов вообще работала.
  • upload_max_filesize — максимальный размер одного файла, который PHP принимает (по умолчанию 2M).
  • post_max_size — максимальный размер всего тела POST-запроса; должен быть больше, чем upload_max_filesize.
  • max_file_uploads — максимальное количество файлов в одном запросе.

Заключение

Загрузка файлов в PHP — фундаментальная часть веб-разработки. С помощью суперглобального массива $_FILES и функции move_uploaded_file() можно реализовать загрузку всего в несколько строк. Сложность заключается в безопасности: всегда проверяйте код ошибки загрузки, ограничивайте размер файла, определяйте реальный MIME-тип через finfo вместо того, чтобы доверять $_FILES[...]['type'], и генерируйте имя файла самостоятельно, чтобы клиент никогда не мог контролировать, куда попадёт файл. Для обработки сохранённого файла впоследствии см. работу с файлами в PHP и валидацию форм.

Практика

Практика
Какие из следующих утверждений о загрузке файлов в PHP верны?
Какие из следующих утверждений о загрузке файлов в PHP верны?
Was this page helpful?