Загрузка файлов на сайт: PHP, AJAX, HTML5 и Drag’n’Drop
Скучные формы загрузки — прошлый век. HTML5 дает возможности, чтобы добавить Drag’n’Drop, а AJAX позволяет загружать файлы без обновления страницы.


vlada_maestro / shutterstock
В основном пользователи смотрят или скачивают информацию с сайтов, но иногда им может быть нужно что-нибудь загрузить самим. Это может быть аватарка, архив с документами, песня, видео со свадьбы или статья в формате .docx.
Программисту нужно позаботиться о том, чтобы посетитель смог сделать это максимально удобно. Загрузить файл на сайт можно и с помощью обычной формы и обработчика на PHP, но с выходом HTML5 появились другие интересные возможности — в этой статье мы поговорим и о базовых функциях, и о нововведениях.
Загрузка файлов на PHP
Начать следует с создания формы:
<form method='post' action="/file.php" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="5000000">
<input type='file' name='file[]' class='file-drop' id='file-drop' multiple required><br>
<input type='submit' value='Загрузить' >
</form>
<div class='message-div message-div_hidden' id='message-div'></div>
Для тега <form> мы указываются следующие атрибуты:
- method — метод отправки данных (в нашем случае post);
- action — путь к обработчику;
- ectype — тип формы; ult значение multipart/form-data дает браузеру понять, что с ее помощью будут отправляться сразу несколько файлов.
Далее в форме создается скрытое поле MAX_FILE_SIZE, где указан максимальный объем загружаемых файлов в байтах. Поле должно предшествовать самому полю для выбора файлов. Это нужно для того, чтобы пользователь узнал, что превысил допустимый лимит на размер файла до того, как дождется загрузки 200-мегабайтного ролика. Но основная проверка все равно должна проводиться обработчиком, потому что значение этого поля можно изменить в браузере.
Во второй тег <input> добавляется атрибут multiple и имя file[] — это позволит с помощью одного поля загрузить сразу несколько файлов. Также в коде присутствует div, в который позже будет выводиться сообщение.
Далее указываются стили CSS:
.file-drop {
background:#fff;
margin:auto;
padding:200px 200px;
border:2px solid #333;
}
.file-drop_dragover {
border:2px dashed #333;
}
.file-drop__input {
border:0;
}
.message-div {
background:#fefefe;
border:2px solid #333;
color:#333;
width:350px;
height:150px;
position:fixed;
bottom:25px;
right:25px;
font-size:15px;
padding:5px;
z-index:99999;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
.message-div_hidden {
right:-9999999999999999999px;
}
И вот как это выглядит:

Форма уже функционирует: можно выбирать или перетаскивать файлы, а после нажатия на кнопку «Загрузить» данные отправятся в обработчик. Там они попадают в многомерный супермассив $_FILES. Его структура выглядит так:
- имя поля, через которое загружен файл;
- name — имя загружаемого файла;
- type — тип в формате MIME-type;
- size — объем в байтах;
- tmp_name — временный адрес;
- error — номер ошибки, если она произошла.
Если через одно поле загружается сразу несколько файлов, то получение доступа к какому-то конкретному происходит следующим образом: $_FILES['file']['name'][0] — индекс файла находится в самом конце.
Сначала нужно провести несколько проверок, и только потом перемещать файлы из временного хранилища непосредственно на сайт. Иначе может получиться так, что взломщики загрузят на сайт PHP-файлы и смогут их запустить, чтобы получить доступ к базе данных или к файловой системе сервера.
Вот как выглядит обработчик:
<?
if(isset($_FILES)) {
$allowedTypes = array('image/jpeg','image/png','image/gif');
$uploadDir = "files/"; //Директория загрузки. Если она не существует, обработчик не сможет загрузить файлы и выдаст ошибку
for($i = 0; $i < count($_FILES['file']['name']); $i++) { //Перебираем загруженные файлы
$uploadFile[$i] = $uploadDir . basename($_FILES['file']['name'][$i]);
$fileChecked[$i] = false;
echo $_FILES['file']['name'][$i]." | ".$_FILES['file']['type'][$i]." — ";
for($j = 0; $j < count($allowedTypes); $j++) { //Проверяем на соответствие допустимым форматам
if($_FILES['file']['type'][$i] == $allowedTypes[$j]) {
$fileChecked[$i] = true;
break;
}
}
if($fileChecked[$i]) { //Если формат допустим, перемещаем файл по указанному адресу
if(move_uploaded_file($_FILES['file']['tmp_name'][$i], $uploadFile[$i])) {
echo "Успешно загружен <br>";
} else {
echo "Ошибка ".$_FILES['file']['error'][$i]."<br>";
}
} else {
echo "Недопустимый формат <br>";
}
}
} else {
echo "Вы не прислали файл!" ;
}
?>
Если загрузка прошла успешно, создается массив разрешенных типов, по которому проверяется соответствие форматов. Затем, если валидация пройдена, с помощью функции move_uploaded_file файл перемещается из временного хранилища в указанную директорию.
Такой код хоть и работает, но он довольно примитивен и его нужно расширять:
- удалить из названия специальные символы и пробелы, а в идеале вообще задать имя самостоятельно;
- занести информацию в базу данных;
- проверить размер файла;
- если это изображение — его можно сжать и так далее.
В общем, стоит провести несколько дополнительных проверок, чтобы файлы не представляли угрозу для сайта и не валялись без дела.
Загрузка файлов на сайт с помощью AJAX
Если добавить возможности JavaScript, форму можно сделать полезнее и красивее. Например, можно будет загружать файлы через AJAX, а также добавить анимацию при перетаскивании:
var fileDrop = document.getElementById('file-drop'); //Получаем объекты
var loadButton = document.getElementById('load-button');
var messageDiv = document.getElementById("message-div");
function dragOver() {//Меняем рамку, когда пользователь перетаскивает файл на поле
fileDrop.setAttribute('class','file-drop file-drop_dragover');
}
function dragOut() {//Возвращаем обычную рамку
fileDrop.setAttribute('class','file-drop');
}
function closeMessage() {//Закрываем сообщение
messageDiv.setAttribute('class','message-div message-div_hidden');
messageDiv.innerHTML = '';
}
function showMessage(data = 0) {//Показываем сообщение о том, что началась или закончилась загрузка
if(data == 0) {
data = "Загрузка...";
} else {
setTimeout(closeMessage,4000);
}
messageDiv.innerHTML = data;
messageDiv.setAttribute('class','message-div');
}
function uploadFile() { //Загружаем файл
dragOut();
var files = this.files;
var data = new FormData();
for(var i = 0; i < files.length; i++) { //Помещаем в дата массив с файлами
data.append(i, files[i]);
}
showMessage(); //Показываем сообщение, что загрузка началась
$.ajax({
url: "/file.php", //Ссылка на обработчик
type: 'POST', //Метод передачи
data: data, //Массив с файлами
cache: false, //Обязательно указать false
processData: false, //Обязательно указать false
contentType: false, // Обязательно указать false
success: function (data) {//В случае успеха показываем сообщение с результатом работы и очищаем поле
showMessage(data);
fileDrop.value = null;
}
});
}
//Указываем события для вызова функций
fileDrop.addEventListener('dragover',dragOver);
fileDrop.addEventListener('dragleave',dragOut);
fileDrop.addEventListener('change',uploadFile); //Отправляем файлы сразу после того, как они будут выбраны
Сначала поле передается в объект fileDrop, а затем создаются функции dragOver, dragOut, showMessage и closeMessage: первые две меняют рамку для тега <input>, а вторые показывают и скрывают сообщение. Затем создается функция uploadFile, которая отправляет файл через AJAX.
Функция получает файлы с помощью объекта FormData, затем показывает сообщение, что началась загрузка, и начинает отправлять файл.
Из-за измененного способа передачи структура супермассива $_FILES немного меняется: теперь сначала указывается индекс файла, а потом — необходимая ячейка.
<?
if(isset($_FILES)) {
$uploadDir = "files/";
$allowedTypes = array('image/jpeg','image/png','image/gif');
for($i = 0; $i < count($_FILES); $i++) {
$uploadFile[$i] = $uploadDir . basename($_FILES[$i]['name']);
$fileChecked[$i] = false;
echo $_FILES[$i]['name']." | ".$_FILES[$i]['type']." — ";
for($j = 0; $j < count($allowedTypes); $j++) {
if($_FILES[$i]['type'] == $allowedTypes[$j]) {
$fileChecked[$i] = true;
break;
}
}
if($fileChecked[$i]) {
if(move_uploaded_file($_FILES[$i]['tmp_name'], $uploadFile[$i])) {
echo "Успешно загружен <br>";
} else {
echo "Ошибка ".$_FILES[$i]['error']."<br>";
}
} else {
echo "Недопустимый формат <br>";
}
}
} else {
echo "Вы не прислали файл!";
}
?>
Вот как работает загрузка файлов на AJAX:

Заключение
Дальше с файлами можно делать все что угодно:
- использовать изображения в статьях и в качестве аватарок;
- добавить плеер, который будет воспроизводить видео и аудио;
- создать текстовый, графический или аудио/видеоредактор;
- разрешать скачивать файлы по ссылке и так далее.