Страница загрузок на PHP: от простого к сложному
Реализация страницы загрузок на PHP
Как создать базовую страницу загрузки одного файла с проверкой расширения и размера?
Наиболее распространённое решение включает HTML-форму с атрибутом enctype="multipart/form-data" и PHP-скрипт, который обрабатывает массив $_FILES. Основные шаги:
- Убедиться, что форма отправлена методом POST.
- Проверить, был ли выбран файл и произошла ли ошибка загрузки (
$_FILES['file']['error'] === UPLOAD_ERR_OK). - Ограничить размер файла (например, проверкой значения
$_FILES['file']['size']). - Проверить расширение файла (разрешить только определённые типы).
- Переместить файл из временной директории в целевую с помощью
move_uploaded_file().
<?php
// upload.php
$targetDir = "uploads/";
$maxFileSize = 2 * 1024 * 1024; // 2 MB
$allowedExtensions = ['jpg', 'png', 'pdf', 'zip'];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
if ($file['error'] !== UPLOAD_ERR_OK) {
// обработка ошибки
$errorMessage = 'Ошибка загрузки файла. Код: ' . $file['error'];
} else {
$originalName = basename($file['name']);
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExtensions)) {
$errorMessage = 'Недопустимое расширение файла.';
} elseif ($file['size'] > $maxFileSize) {
$errorMessage = 'Файл превышает максимальный размер.';
} else {
$destination = $targetDir . $originalName;
if (file_exists($destination)) {
$errorMessage = 'Файл с таким именем уже существует.';
} else {
if (move_uploaded_file($file['tmp_name'], $destination)) {
$successMessage = 'Файл успешно загружен: ' . $originalName;
} else {
$errorMessage = 'Не удалось переместить загруженный файл.';
}
}
}
}
}
?>
Downloads php page (страница загрузок на php)
Типичные проблемы:
- Ошибка UPLOAD_ERR_INI_SIZE / UPLOAD_ERR_FORM_SIZE – превышен лимит размера в php.ini или в форме. Решение: увеличить
upload_max_filesizeиpost_max_sizeв php.ini или настройках хостинга. - Проблемы с правами доступа – папка назначения должна быть доступна для записи веб-сервером (обычно chmod 755 или 775).
- Ложное расширение – злоумышленник может переименовать вредоносный файл в .jpg. Для повышения безопасности рекомендуется проверять MIME-тип через
mime_content_type()илиfinfo.
Вариант 1: Загрузка нескольких файлов одновременно
Как организовать загрузку нескольких файлов через одно поле формы?
Используйте массив в имени input: name="files[]" и атрибут multiple. В PHP массив $_FILES['files'] будет содержать вложенные массивы. Обработка ведётся в цикле.
<input type="file" name="files[]" multiple>
<?php
if (isset($_FILES['files'])) {
$files = $_FILES['files'];
$count = count($files['name']);
for ($i = 0; $i < $count; $i++) {
if ($files['error'][$i] === UPLOAD_ERR_OK) {
$tmp = $files['tmp_name'][$i];
$name = basename($files['name'][$i]);
// проверки и move_uploaded_file
}
}
}
?>
Проблема: превышение лимита max_file_uploads в php.ini (по умолчанию 20). Решение: увеличить значение.
Вариант 2: Проверка MIME-типа для безопасности
Как защититься от загрузки вредоносных файлов, маскирующихся под изображения?
Используйте расширение Fileinfo для проверки реального MIME-типа. Пример:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowedMimes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mime, $allowedMimes)) {
// отклоняем файл
}
Ошибка: некоторые файлы (например, SVG) могут иметь текстовый MIME, что требует дополнительной логики.
Вариант 3: Переименование файла для избежания конфликтов
Как избежать перезаписи файлов с одинаковыми именами?
Генерируйте уникальное имя через uniqid() или md5(uniqid()).'.'.$ext. Либо добавляйте временную метку.
$newName = time() . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
$destination = $targetDir . $newName;
Проблема: при большом количестве файлов коллизии маловероятны, но для абсолютной уникальности используйте UUID.
Вариант 4: Асинхронная загрузка через AJAX (без перезагрузки страницы)
Как реализовать загрузку файла без обновления страницы?
На фронтенде используйте FormData и XMLHttpRequest (или fetch). PHP скрипт возвращает JSON-ответ.
// JavaScript
let formData = new FormData();
formData.append('file', fileInput.files[0]);
fetch('upload_ajax.php', {
method: 'POST',
body: formData
}).then(response => response.json())
.then(data => console.log(data));
<?php
// upload_ajax.php
header('Content-Type: application/json');
// … проверки …
if (move_uploaded_file(...)) {
echo json_encode(['success' => true, 'filename' => $newName]);
} else {
echo json_encode(['success' => false, 'error' => 'Ошибка']);
}
?>
Проблема: CORS, если скрипт лежит на другом домене. Решение: установить заголовки Access-Control-Allow-Origin.
Вариант 5: Создание миниатюр для загруженных изображений
Как автоматически создавать превью загруженных изображений?
Используйте библиотеку GD или Imagick. Пример с GD:
$src = imagecreatefromjpeg($destination);
$thumbWidth = 200;
$thumbHeight = 150;
$thumb = imagecreatetruecolor($thumbWidth, $thumbHeight);
$origW = imagesx($src);
$origH = imagesy($src);
imagecopyresampled($thumb, $src, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $origW, $origH);
imagejpeg($thumb, $targetDir . 'thumb_' . $newName);
imagedestroy($src);
imagedestroy($thumb);
Ошибка: если изображение не JPEG, нужно использовать соответствующие функции (imagecreatefrompng, imagecreatefromgif).
Расширенные примеры реализации страницы загрузок
Пример 1: Загрузка с отображением прогресса (на основе сессии и JavaScript)
Для отслеживания прогресса загрузки большого файла можно использовать механизм uploadprogress (PECL) или более простой подход с опросом сервера через отдельный скрипт, который читает сессию. Однако популярный способ в современных проектах – использование библиотек на стороне клиента (например, Dropzone.js) с собственным PHP-обработчиком. Ниже показан минимальный вариант с периодическим опросом размера уже записанного файла (только для демонстрации концепции, не рекомендуется для production).
<!-- index.html -->
<input type="file" id="fileInput">
<progress id="progressBar" value="0" max="100"></progress>
<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
var file = e.target.files[0];
var formData = new FormData();
formData.append('file', file);
var xhr = new XMLHttpRequest();
// Прогресс загрузки на сторону сервера (upload progress)
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
var percent = (e.loaded / e.total) * 100;
document.getElementById('progressBar').value = percent;
}
};
xhr.onload = function() {
if (xhr.status === 200) {
document.getElementById('progressBar').value = 100;
}
};
xhr.open('POST', 'upload_progress.php', true);
xhr.send(formData);
});
</script>
<?php
// upload_progress.php
session_start();
$target = 'uploads/' . uniqid() . '_' . basename($_FILES['file']['name']);
if (move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
echo json_encode(['status' => 'ok', 'file' => $target]);
} else {
echo json_encode(['status' => 'error']);
}
?>
Результат: прогресс-бар обновляется в реальном времени (зависит от браузера и сети).
Пример 2: Загрузка файла с записью информации в базу данных (MySQL)
Позволяет хранить метаданные о загруженных файлах: имя, размер, дата загрузки, MIME-тип. Сам файл обычно сохраняется в файловой системе, а в БД – только путь.
<?php
// upload_to_db.php (только пример)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
// ... проверки ...
$stmt = $pdo->prepare("INSERT INTO files (original_name, saved_name, size, mime, created_at) VALUES (?, ?, ?, ?, NOW())");
$stmt->execute([
$originalName,
$savedName,
$file['size'],
$mime
]);
// После записи в БД перемещаем файл в папку
move_uploaded_file($file['tmp_name'], 'uploads/' . $savedName);
?>
Результат: в таблице files появляется запись с ID, по которому можно ссылаться на файл.
Пример 3: Пакетная загрузка с очередью и последовательной обработкой
Когда необходимо загрузить много файлов и выполнить с ними дополнительные действия (например, конвертировать, создать несколько версий), удобно использовать очереди (RabbitMQ, Redis) или просто фоновые скрипты. Упрощённый вариант: все файлы сначала перемещаются во временную папку, а затем cron-запускает обработчик.
// В обработчике загрузки (upload_handler.php)
$tempDir = 'temp_uploads/';
foreach ($_FILES['files']['tmp_name'] as $i => $tmp) {
$tempPath = $tempDir . uniqid() . '_' . $_FILES['files']['name'][$i];
move_uploaded_file($tmp, $tempPath);
// Добавляем задачу в очередь (например, файл-лист)
file_put_contents('queue.txt', $tempPath . PHP_EOL, FILE_APPEND);
}
// Обработчик (process_queue.php), запускаемый по расписанию:
$queue = file('queue.txt', FILE_IGNORE_NEW_LINES);
foreach ($queue as $path) {
if (file_exists($path)) {
// Выполнить обработку (создать миниатюры, проверить вирусы и т.д.)
// Затем переместить в конечную директорию
rename($path, 'uploads/' . basename($path));
}
}
file_put_contents('queue.txt', ''); // очистить
Результат: большой объём файлов не блокирует работу пользователя, обработка происходит в фоне.