Загрузка файлов в PHP
Узнайте, как реализовать загрузку файлов в PHP с помощью $_FILES и move_uploaded_file() — с валидацией и настройками безопасности.
Загрузка файлов — распространённое требование в веб-разработке. Будь то фотография профиля, PDF-документ или CSV-импорт, возможность отправлять файлы на сервер является ключевой функцией большинства приложений. В PHP это реализуется с помощью функции move_uploaded_file() и суперглобального массива $_FILES.
В этой главе рассматривается полный процесс загрузки: настройка HTML-формы, чтение метаданных загруженного файла из $_FILES, его безопасная валидация и перемещение в конечное место. Также описываются коды ошибок загрузки и настройки конфигурации PHP, управляющие ограничениями на загрузку.
Как работает загрузка файлов в PHP
Загрузка файла происходит в три этапа:
- Браузер отправляет файл в POST-запросе с типом
multipart/form-data. - PHP получает файл и записывает его во временное место на диске, после чего предоставляет информацию о нём через массив
$_FILES. - Ваш скрипт проверяет файл и перемещает его из временного места в постоянное с помощью
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_OK | 0 | Ошибок нет, файл загружен успешно. |
UPLOAD_ERR_INI_SIZE | 1 | Файл превышает upload_max_filesize в php.ini. |
UPLOAD_ERR_FORM_SIZE | 2 | Файл превышает значение поля MAX_FILE_SIZE в форме. |
UPLOAD_ERR_PARTIAL | 3 | Файл был загружен лишь частично. |
UPLOAD_ERR_NO_FILE | 4 | Файл не был загружен. |
UPLOAD_ERR_NO_TMP_DIR | 6 | Отсутствует временная папка. |
UPLOAD_ERR_CANT_WRITE | 7 | Не удалось записать файл на диск. |
Безопасная проверка типа и размера
Не полагайтесь на $_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 и валидацию форм.