Асинхронная загрузка файлов на сервер PHP + AJAX
Про загрузку файлов на сервер я писал здесь. Сегодня я расскажу про асинхронную загрузку файлов на сервер.
В настоящее время довольно популярным решением для веб-сайтов является работа пользователя со страницей без ее перезагрузки. В большинстве своём это делается с помощью Ajax
– технологии асинхронного взаимодействия с сервером, основанной на объекте XMLHttpRequest
.
В этой статье рассмотрим простое решение одной из самых распространенных задач – асинхронная загрузка файла на сервер при помощи PHP. Задача будет следующая: На странице (предположим, что это страница в личном кабинете пользователя) есть форма с input
типа file
и кнопка Отправить
. Ниже находится контейнер с картинкой (аватар пользователя по задумке) в который асинхронно подгружается картинка после выполнения AJAX запроса. И по умолчанию она просто выводится из базы данных. То есть, при загрузке картинки новое сгенерированное название будет записываться в БД, сама картинка под этим названием загружаться на сервер и выводится в контейнере взамен предыдущей. И всё это асинхронно.
Сразу скажу, что я по бОльшей степени преследовал цель описать сам процесс загрузки, особо не сосредотачиваясь на "элегантности" кода ). Кто как захочет использовать этот код. Кто-то кусочек нужный возьмёт, кто-то целый класс напишет на его основе, кто-то переделает для себя, кто-то вовсе не будет его использовать никак. Тем более, там действительно, нужна доработка под боевые задачи. Например, валидацию на MIME-типы (и другие проверки) нужно делать ещё в JS до попадания данных скрипту PHP. В скрипт данные должны приходить отвалидированные. Или при загрузке картинки удалять на сервере старую картинку. Поэтому я решил, что полезнее будет подробно описать процесс, нежели допиливать до идеала то, что каждый для себя доработает как нужно. Поехали.
Безусловно, подключаем jQuery (если ещё в проекте нигде данная библиотека не подключена):
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
Простая разметка HTML формы и картинка из базы. Предположим, что у вас уже есть база данных и в базе уже есть таблица (например, user
). В блоке с id="photo-content"
выводится картинка из базы. Я весь этот код помещу в файле index.php
.
<? $db = new PDO('mysql:host=localhost;dbname=test', 'root', ''); $query = "SELECT `avatar` FROM `user` WHERE `id` = ?"; $id = 1; $stmt = $db->prepare($query); $stmt->execute([$id]); $image = $stmt->fetchColumn(); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Ajax Upload</title> <link rel="stylesheet" href="main.css"> </head> <body> <div id="wrapper"> <h1>Image upload</h1> <form method="post" enctype="multipart/form-data" id="form-file-ajax" action="upload.php"> <input type="file" id="file" name="file" required> <br/> <button type="submit" id="btn-file-upload">Загрузить</button> <!-- preloader.gif - картинка имитирующая процесс загрузки --> <div id="process"><img src="preloader.gif" alt="Loading"></div> <div id="photo-content"> <!-- Эта картинка выводится из базы по умолчанию --> <img src="http://site.com/upload/<?= $image ?>" alt="Image" width="400"> </div> </form> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <!-- Этот файл будет содержать код отправки данных PHP скрипту --> <script src="ajax.js"></script> </div> </body> </html>
Немного CSS стилей:
#wrapper{ width: 60%; margin: 20px auto; } form button{ margin-bottom: 50px; } input[type=text], input[type=file]{ margin-bottom: 20px; } #process { display: none; }
В файле ajax.js
:
$(document).ready(function(){ $("#form-file-ajax").on('submit', function(e){ e.preventDefault(); var formData = new FormData(); var form = $(this); formData.append('file', $('#file').prop("files")[0]); $.ajax({ url: form.attr('action'), type: form.attr('method'), processData: false, contentType: false, cache:false, dataType : 'text', data: formData, // Будет вызвана перед осуществлением AJAX запроса beforeSend: function(){ $('#process').fadeIn(); }, // будет вызвана после завершения ajax-запроса // (вызывается позднее функций-обработчиков успешного (success) или аварийного (error) complete: function () { $('#process').fadeOut(); }, success: function(data){ //form[0].reset(); data = JSON.parse(data); var image = '<div class="img-item"><img src="http://site.com/upload/'+data.file+'" width="400"></div>'; var photoContent = $("#photo-content"); photoContent.html(''); photoContent.append(image); }, error: function(data){ console.log(data); } }); }); });
В файле upload.php
:
<?php if(isset($_FILES['file'])) { if ($_FILES['file']['name'] !== '' && $_FILES['file']['error'] == 0) { try { // MIME-типы нужно проверять ещё в JS коде и выводить ошибки пользователю // Сейчас они вываливаются во вкладке "Network" браузера $fileTmpName = $_FILES['file']['tmp_name']; $fi = finfo_open(FILEINFO_MIME_TYPE); $mime = (string) finfo_file($fi, $fileTmpName); if (strpos($mime, 'image') === false) die('Можно загружать только изображения с расширениями .jpg, .jpeg, .png!'); $image = getimagesize($fileTmpName); $extension = image_type_to_extension($image[2]); $name = randomFileName($extension); $file = $name.str_replace('jpeg', 'jpg', $extension); if (!move_uploaded_file($fileTmpName, __DIR__ . '/upload/'.$file)) { throw new Exception('При загрузке изображения произошла ошибка на сервере!'); } // Записать имя файла в БД $db = new PDO('mysql:host=localhost;dbname=ajax', 'root', 'password'); $user_id = 1; $query = "UPDATE `user` SET `avatar` = :avatar WHERE `id` = :user_id"; $params = [':avatar' => $file, ':user_id' => $user_id]; $stmt = $db->prepare($query); if (!$stmt->execute($params)) { throw new Exception('Произошла ошибка при записи в БД!'); } // Записать в $data имя файла $data = ['file' => $file]; echo json_encode($data); } catch (Exception $e) { die($e->getMessage()); } } } // Генерируем уникальное имя для файла function randomFileName($extension = false) { $extension = $extension ? '.' . $extension : ''; do { $name = md5(microtime() . rand(0, 9999)); $file = $name . $extension; } while (file_exists($file)); return $file; }
Вот и все. Ясное дело, что это не идеальное решение и как использовать его (если использовать) решать каждому самостоятельно. Может быть в дальнейшем я напишу специальный класс со всеми недочётами. А пока, как-то так.
Для работы данного примера без ошибок не забудьте заменить все адреса своими и указать корректные доступы к базе данных!