PHP загрузка файла: как перенести и безопасно сохранить файл на сервере
В PHP задачу move uploaded file решает функция move_uploaded_file, которая переносит принятый файл из временного каталога. PHP загрузка файла на сервер начинается с формы с multipart/form-data и обработчика, который проверяет ошибку, размер и содержимое. Вы разберете массив $_FILES, лимиты PHP, безопасное имя и два варианта мультизагрузки.
PHP загрузка файла на сервер: простая форма
HTML-форма отправляет файл методом POST, если у нее указаны правильные метод и тип кодирования. Атрибут name задает ключ, по которому PHP найдет файл в $_FILES.
<form method="post" action="basic.php" enctype="multipart/form-data">
<label for="inputfile">Выберите файл</label>
<input type="file" id="inputfile" name="inputfile">
<button type="submit">Загрузить</button>
</form>
После отправки PHP положит данные поля inputfile в $_FILES['inputfile']. Обработчик проверит метод запроса, наличие поля, код ошибки и факт загруженного файла.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['inputfile'])) {
$file = $_FILES['inputfile'];
if ($file['error'] === UPLOAD_ERR_OK && is_uploaded_file($file['tmp_name'])) {
$name = bin2hex(random_bytes(16));
$destination_path = __DIR__ . '/uploads/' . $name;
if (move_uploaded_file($file['tmp_name'], $destination_path)) {
echo 'File uploaded';
} else {
echo 'File not uploaded';
}
} else {
echo 'Upload error: ' . $file['error'];
}
}Для этого учебного примера заранее создайте каталог uploads рядом с basic.php. Пользователь, под которым работает PHP, должен иметь право записи в каталог.
Этот пример показывает механику move uploaded file php. Он еще не проверяет MIME и расширение, поэтому публичный сайт должен использовать обработчик из раздела о безопасности.
Файлы в PHP: что хранит $_FILES?
$_FILES – глобальный массив PHP с данными файлов из POST-запроса.
Ключ массива совпадает с атрибутом name у поля формы.
Array
(
[inputfile] => Array
(
[name] => photo.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpabc123
[error] => 0
[size] => 6887
)
)| Поле | Что хранит | Как использовать |
|---|---|---|
name | Исходное имя файла | Не использовать как имя на сервере |
type | MIME, присланный клиентом | Не считать защитной проверкой |
tmp_name | Путь к временному файлу | Передать в finfo_file и move_uploaded_file |
error | Код результата загрузки | Сначала проверить его значение |
size | Размер файла в байтах | Сравнить с серверным лимитом |
Браузер присылает поле type, поэтому клиент может подменить его. Проверяйте содержимое через Fileinfo и разрешайте только заранее выбранные расширения и MIME-типы.
| Константа | Значение | Что значит | Что сделать |
|---|---|---|---|
UPLOAD_ERR_OK | 0 | Файл принят без ошибки | Продолжить проверки |
UPLOAD_ERR_INI_SIZE | 1 | Размер превысил upload_max_filesize | Показать ограничение |
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 | Сервер не записал файл | Проверить права каталога |
UPLOAD_ERR_EXTENSION | 8 | Расширение PHP остановило загрузку | Проверить настройки расширений |
Что такое move_uploaded_file?
Функция move_uploaded_file переносит файл из временного каталога PHP в указанное место. Она возвращает true, если перенос завершился успешно, и false, если сервер не смог его выполнить.
if (move_uploaded_file($file['tmp_name'], $destination_path)) {
echo 'File uploaded';
} else {
echo 'File not uploaded';
}Функция принимает временный путь и путь назначения.
Перед переносом проверьте код ошибки, размер, содержимое и результат is_uploaded_file.
Изменяем лимит размера загружаемого файла
PHP ограничивает загрузку несколькими директивами. Значение upload_max_filesize задают в php.ini, .htaccess или .user.ini. Обычный ini_set внутри обработчика эту директиву не меняет.
upload_max_filesize = 10M
post_max_size = 12Mpost_max_size задает предел всего POST-запроса, поэтому его ставят выше upload_max_filesize. При превышении post_max_size PHP передаст пустые массивы $_POST и $_FILES.
Скрытое поле называется MAX_FILE_SIZE и хранит лимит в байтах. PHP может вернуть UPLOAD_ERR_FORM_SIZE, но клиент меняет это поле. Сервер все равно сравнивает $_FILES['inputfile']['size'] со своим лимитом.
<input type="hidden" name="MAX_FILE_SIZE" value="10485760">Размещайте MAX_FILE_SIZE перед полем выбора файла внутри формы. Для долгих загрузок учитывайте max_execution_time и max_input_time. Если код держит файл в памяти, проверьте также memory_limit.
Как обезопасить PHP-скрипт загрузки файлов
Безопасный обработчик не доверяет имени, расширению и MIME от браузера. Он проверяет размер по $_FILES['size'], читает MIME через Fileinfo и принимает только разрешенные сочетания расширения и содержимого.
Сначала подготовьте каталог вне веб-корня – папки, доступной через браузер. Команды для /srv/private_uploads рассчитаны на Linux-сервер с SSH-доступом. Для создания каталога и смены владельца нужны права администратора.
Определите пользователя PHP через временный файл php-user.php рядом с обработчиком. Этот способ требует включенного расширения POSIX в PHP.
<?php
$user = posix_getpwuid(posix_geteuid());
echo htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8');Откройте этот файл через браузер по адресу вашего сайта. Страница покажет имя пользователя, от которого PHP обрабатывает веб-запрос. Скопируйте имя, затем удалите диагностический файл с сервера.
Не запускайте его через консоль: консольный PHP может работать от другого пользователя.
- Откройте терминал на своем компьютере. Подключитесь командой
ssh login@host, заменивloginиhostучетными данными SSH вашего сервера. После входа команды выполняются на сервере. - Создайте хранилище командой
sudo mkdir -p /srv/private_uploads. Введите пароль своей серверной учетной записи, еслиsudoзапросит его. Учетная запись должна иметь разрешение на выполнение административных команд. - Назначьте владельца командой
sudo chown PHP_USER /srv/private_uploads. ЗаменитеPHP_USERименем, которое показал диагностический файл. - Ограничьте доступ командой
sudo chmod 750 /srv/private_uploads. Владелец получит право записи, а остальные пользователи вне группы потеряют доступ.
Эти команды предназначены для нового отдельного каталога загрузок. Если вы вошли как root, выполняйте их без sudo.
Без административного доступа используйте уже доступный PHP каталог вне веб-корня. Укажите его абсолютный путь вместо /srv/private_uploads во всех вызовах saveUpload. Для проверки создайте рядом с обработчиком временный PHP-файл с этим кодом:
<?php
$uploadDir = '/absolute/path/to/private_uploads';
echo is_dir($uploadDir) && is_writable($uploadDir)
? 'Каталог доступен для записи'
: 'Каталог отсутствует или запись запрещена';Замените путь своим, затем откройте файл через браузер. Сообщение «Каталог доступен для записи» подтверждает доступ текущего процесса PHP. После проверки удалите временный файл.
<?php
function saveUpload(array $file, array $allowed, int $maxSize, string $uploadDir): string
{
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException('Ошибка загрузки: ' . $file['error']);
}
if ($file['size'] > $maxSize) {
throw new RuntimeException('Файл превышает допустимый размер');
}
if (!is_uploaded_file($file['tmp_name'])) {
throw new RuntimeException('Источник файла не прошел проверку');
}
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!isset($allowed[$extension])) {
throw new RuntimeException('Расширение файла запрещено');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
if ($mime === false || !in_array($mime, $allowed[$extension], true)) {
throw new RuntimeException('Содержимое файла не соответствует типу');
}
$newName = bin2hex(random_bytes(16)) . '.' . $extension;
$destination_path = rtrim($uploadDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $newName;
if (!move_uploaded_file($file['tmp_name'], $destination_path)) {
throw new RuntimeException('Сервер не сохранил файл');
}
return $newName;
}
$allowed = [
'jpg' => ['image/jpeg'],
'jpeg' => ['image/jpeg'],
'png' => ['image/png'],
'txt' => ['text/plain']
];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['inputfile'])) {
try {
$savedName = saveUpload($_FILES['inputfile'], $allowed, 1048576, '/srv/private_uploads');
echo 'Файл сохранен: ' . htmlspecialchars($savedName, ENT_QUOTES, 'UTF-8');
} catch (RuntimeException $error) {
echo htmlspecialchars($error->getMessage(), ENT_QUOTES, 'UTF-8');
}
}Функция возвращает имя сохраненного файла, а ошибки передает через RuntimeException. Обработчик ловит исключение и выводит его текст после экранирования. Сохраните обработчик в basic.php перед HTML-формой одиночной загрузки.
Имя формирует random_bytes, поэтому исходное имя не перезаписывает соседний файл. Расширение берется из белого списка, а результат переноса проверяет условие.
Если каталог находится внутри веб-корня, запретите исполнение PHP для него на уровне веб-сервера.
Мультизагрузка файлов при помощи PHP-скрипта
Есть 2 способа принять несколько файлов: использовать разные имена полей или одно поле с массивом name[] и атрибутом multiple. В обоих случаях каждый файл проходит отдельную проверку.
Разные имена полей
Такой вариант подходит, когда одно поле принимает фотографию, а другое – документ. В этом примере резюме загружается как обычный текстовый файл TXT.
У каждого поля должен быть свой id и свой ключ в $_FILES.
<form method="post" enctype="multipart/form-data">
<label for="profilepic">Фото</label>
<input type="file" id="profilepic" name="profilepic">
<label for="resume">Резюме в формате TXT</label>
<input type="file" id="resume" name="resume">
<button type="submit">Загрузить</button>
</form>Передайте каждое поле функции saveUpload из безопасного обработчика.
Создайте отдельный PHP-файл с функцией saveUpload, обработчиком и этой формой. Скопируйте только определение функции, без блока обработки поля inputfile.
<?php
$rules = [
'profilepic' => [
'jpg' => ['image/jpeg'],
'jpeg' => ['image/jpeg'],
'png' => ['image/png']
],
'resume' => [
'txt' => ['text/plain']
]
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
foreach ($rules as $field => $allowed) {
if (!isset($_FILES[$field])) {
echo 'Поле не найдено: ' . htmlspecialchars($field, ENT_QUOTES, 'UTF-8');
continue;
}
try {
$name = saveUpload($_FILES[$field], $allowed, 5242880, '/srv/private_uploads');
echo 'Сохранен файл ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
} catch (RuntimeException $error) {
echo htmlspecialchars($error->getMessage(), ENT_QUOTES, 'UTF-8');
}
}
}Массив name[] и атрибут multiple
Один элемент формы принимает несколько файлов, если у него есть имя с квадратными скобками и атрибут multiple. Индекс связывает имя, временный путь, ошибку и размер одного файла.
<form method="post" enctype="multipart/form-data">
<label for="pic">Файлы</label>
<input type="file" id="pic" name="pic[]" multiple>
<button type="submit">Загрузить</button>
</form>Для этого варианта также поместите определение saveUpload перед обработчиком. Форму разместите после PHP-кода в том же файле.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['pic'])) {
$files = $_FILES['pic'];
$allowed = [
'jpg' => ['image/jpeg'],
'jpeg' => ['image/jpeg'],
'png' => ['image/png']
];
foreach ($files['error'] as $index => $error) {
$file = [
'name' => $files['name'][$index],
'type' => $files['type'][$index],
'tmp_name' => $files['tmp_name'][$index],
'error' => $error,
'size' => $files['size'][$index]
];
try {
$name = saveUpload($file, $allowed, 1048576, '/srv/private_uploads');
echo 'Файл сохранен: ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
} catch (RuntimeException $uploadError) {
echo 'Ошибка файла ' . ($index + 1) . ': ' . htmlspecialchars($uploadError->getMessage(), ENT_QUOTES, 'UTF-8');
}
}
}Цикл сохраняет каждый файл отдельно. Если один элемент содержит ошибку, обработчик выводит ее для соответствующего индекса и продолжает обработку остальных файлов.
Как проверить результат загрузки
Откройте форму, выберите разрешенный файл и отправьте ее. Страница покажет сгенерированное имя, а файл появится в каталоге хранения.
Проверьте отказ в трех случаях: выберите слишком большой файл, переименуйте файл с запрещенным расширением и отправьте файл с неподходящим содержимым. Обработчик должен вывести ошибку и не создать новый файл.
Совет от нашей редакции: держите загруженные файлы вне веб-корня, а пользователю показывайте только сгенерированное имя или отдельный идентификатор записи.
Популярное за неделю
- Как создать сайт самому в 2026: 4 способа + пошаговая инструкция 34 мин чтения
- Лучшие конструкторы для создания сайтов — плюсы, минусы и сравнения 22 мин чтения
- Регулярные выражения PHP: синтаксис и примеры 10 мин чтения
- Как создать формы регистрации и авторизации на PHP 5 мин чтения
- Как сделать редирект на PHP 6 мин чтения
- Адаптивный помесячный календарь на AJAX и PHP 2 мин чтения
Комментарии
Здравствуйте! Как мне указать путь сохранения для файла не в ту же директорию где лежит файл php, а в другую например:z:\home\localhost\www\rusakov\images\
Спасибо за такую всеобъемлющую статью, полезно
Подскажите, как можно загрузить файл на свой сервер, в том случае если у меня есть только ссылка на него. То-есть мне нужно форма для загрузки файлов на сервер, в котором можно вставлять ссылку на файл.
Подкажите, как можно сделать так, чтобы каждый раз создавалась новая папка, куда будут помещаться файлы? Например 28122017-1645 (то есть состояла из даты и времени) ?
http://php.net/manual/ru/function.date.php
А если вместо повтора строк тега <input type="file" id="pic" name="pic[]"> изменить его на <input type="file" id="pic" name="pic[]" multiple >, то должна получиться мультизагрузка