Проблема, которая преследует веб-разработчиков в течение многих лет, заключается в том, как добавлять информацию в реальном времени в свои приложения, например индикатор выполнения для загрузки файлов. Пользователи нетерпеливы; они не хотят сидеть и ждать, пока браузер что-то делает, и гадают, не зависло ли оно или у них медленное соединение. Предоставление индикатора прогресса дает пользователям полезную информацию и позволяет им точно знать, что происходит.
Сначала вы можете подумать, что выполнить это можно легко, сначала получив размер файла с компьютера пользователя, а затем выполнив несколько простых вычислений для каталога на сервере, куда загружается файл. Если подумать, вы обнаружите, что все не так просто.
JavaScript может получить доступ к имени файла, типу и даже ширине и высоте локального изображения, но только в HTML5 он смог получить доступ к размеру файла . К сожалению, HTML5 все еще не является законченным стандартом и не поддерживается одинаково во всех браузерах. Альтернативным решением является использование плагина Flash, Java или ActiveX; нет, спасибо, я сдам. Еще одно решение — установить расширение Alternative PHP Cache , но оно может быть недоступно в зависимости от среды вашего хостинга, и это кажется излишним для такой небольшой задачи, как эта.
Казалось бы, все варианты чреваты неприятностями, и задача быстро стала головной болью. Но, по словам Йоды, «нет… есть другое».
 Одна из многих причин, по которым я люблю PHP, заключается в том, что он облегчает, казалось бы, сложные задачи.  Начиная с PHP 5.4, они сделали это снова с новым набором директив конфигурации, session.upload_progress . 
В этой статье я покажу вам, как эту функцию можно использовать для создания простого индикатора выполнения загрузки без каких-либо внешних библиотек или зависимостей браузера. Сначала я расскажу, как это работает, а затем проведу вас через создание четырех файлов, необходимых для выполнения задачи (форма загрузки, немного JavaScript, немного CSS и файл для возврата статуса загрузки).
Процесс загрузки сеанса
  Помимо обычных требований разрешить загрузку файлов, есть еще два, чтобы отслеживать прогресс.  Директива session.upload_progress.enabled должна быть включена, и в вашей веб-форме должно быть скрытое поле с именем, указанным в директиве session.upload_progress.name .  Если для session.upload_progress.enabled true (как по умолчанию в PHP 5.4 и, вероятно, за его пределами) и во время загрузки отправляется $_POST[ session.upload_progress.name ] , информация о передаче файла становится доступной в $_SESSION выражении $_SESSION массив. 
  Вывод print_r() массива $_SESSION будет выглядеть примерно так во время передачи файла: 
массив ( [upload_progress_myForm] => Массив ( [start_time] => 1323733740 [content_length] => 721127769 [bytes_processed] => 263178326 [сделано] => [files] => Array ( [0] => Массив ( [field_name] => userfile [name] => ubuntu-10.04.3-desktop-i386.iso [tmp_name] => [ошибка] => 0 [сделано] => [start_time] => 1323733740 [bytes_processed] => 263178026 ) ) ) )
  Когда вы разрабатываете локально или в быстрой сети и загружаете небольшие файлы, вы не сможете визуально наблюдать за прогрессом, потому что передача происходит так быстро.  В этом случае вы можете попробовать перенести большой файл.  Убедитесь, что настройки в вашем файле php.ini разрешают большие загрузки, в частности директивы post_max_size и upload_max_filesize , а затем убедитесь, что они являются нормальными значениями, когда вы приступаете к работе. 
Создать форму
Первый файл, который я представлю, это форма загрузки. Просто для простоты, пример будет публиковать для себя и обрабатывать только один файл за раз. Кроме того, я не буду беспокоиться о сохранении файла после его загрузки.
  Вот код для form.php : 
 <?php if ($_SERVER["REQUEST_METHOD"] == "POST" && !empty($_FILES["userfile"])) { // move_uploaded_file() } ?> <html> <head> <title>File Upload Progress Bar</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div id="bar_blank"> <div id="bar_color"></div> </div> <div id="status"></div> <form action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="POST" id="myForm" enctype="multipart/form-data" target="hidden_iframe"> <input type="hidden" value="myForm" name="<?php echo ini_get("session.upload_progress.name"); ?>"> <input type="file" name="userfile"><br> <input type="submit" value="Start Upload"> </form> <iframe id="hidden_iframe" name="hidden_iframe" src="about:blank"></iframe> <script type="text/javascript" src="script.js"></script> </body> </html> 
В этом примере код для фактической обработки файла был опущен для упрощения работы. Если вас интересует, как должен выглядеть такой код, ознакомьтесь со статьей « Загрузка файлов с помощью PHP » Тимоти Борончика.
После раздела заголовка, в котором содержится заголовок страницы и таблица стилей, вы увидите небольшую коллекцию элементов div. Div с идентификатором «bar_blank» является контейнером для индикатора выполнения. Div с идентификатором «bar_color» будет динамически обновляться по мере загрузки файла. Div «status» будет отображать числовое значение загруженного процента.
  Форма настроена на отправку по тому же URL, а ее целевой атрибут указывает на скрытый элемент iframe.  Отправка формы в скрытый фрейм позволяет удерживать посетителя на той же странице, пока работа выполняется в фоновом режиме.  На самом деле, это обычная практика при «загрузке Ajax-файла», так как невозможно отправить содержимое файла напрямую с помощью объекта JavaScript XmlHttpRequest . 
  Внутри формы появляется специальное скрытое поле, необходимое для заполнения массива $_SESSION , за которым следует кнопка ввода и отправки файла для загрузки.  Отправка формы вызовет функцию JavaScript с именем startUpload() которая будет определяться включенным файлом JavaScript. 
  Внизу страницы находится скрытый фрейм, в котором будет размещена форма, и импорт файла script.js . 
Добавить стиль
  Следующий файл style.css довольно прост.  Я определил размер контейнера индикатора выполнения и присвоил ему черную рамку размером 1 пиксель, цвет индикатора выполнения при загрузке, а также iframe и индикатор выполнения скрыты. 
 #bar_blank { border: solid 1px #000; height: 20px; width: 300px; } #bar_color { background-color: #006666; height: 20px; width: 0px; } #bar_blank, #hidden_iframe { display: none; } 
Функциональность на стороне клиента
  Файл script.js является самым большим из группы файлов.  Он содержит шесть функций, которые я расскажу ниже.  Многим нравится использовать jQuery для предоставления некоторых функций здесь, и вы, безусловно, можете сделать это, если хотите, но я лично предпочитаю подход старой школы.  Подобно тому, как японцы придают большее значение товарам ручной работы, я просто чувствую большую страсть к коду, если он мой. 
 function toggleBarVisibility() { var e = document.getElementById("bar_blank"); e.style.display = (e.style.display == "block") ? "none" : "block"; } function createRequestObject() { var http; if (navigator.appName == "Microsoft Internet Explorer") { http = new ActiveXObject("Microsoft.XMLHTTP"); } else { http = new XMLHttpRequest(); } return http; } function sendRequest() { var http = createRequestObject(); http.open("GET", "progress.php"); http.onreadystatechange = function () { handleResponse(http); }; http.send(null); } function handleResponse(http) { var response; if (http.readyState == 4) { response = http.responseText; document.getElementById("bar_color").style.width = response + "%"; document.getElementById("status").innerHTML = response + "%"; if (response < 100) { setTimeout("sendRequest()", 1000); } else { toggleBarVisibility(); document.getElementById("status").innerHTML = "Done."; } } } function startUpload() { toggleBarVisibility(); setTimeout("sendRequest()", 1000); } (function () { document.getElementById("myForm").onsubmit = startUpload; })(); 
  Функция toggleBarVisibility() устанавливает соответствующий стиль в div «bar_blank» для отображения или скрытия индикатора выполнения по мере необходимости.  Первоначально он начинается скрытно, но будет отображаться после начала загрузки и снова скрываться после завершения загрузки. 
  Функция createRequestObject() создает объект XMLHttpRequest или ActiveXObject на основе браузера пользователя.  Вероятно, это функция, которую большинство людей хотели бы использовать в jQuery или какой-либо другой среде JavaScript. 
  Функция sendRequest() запрашивает файл progress.php с помощью запроса GET, а затем вызывает handleResponse() для обработки возвращаемых данных. 
  Функция handleResponse() обрабатывает ответ от файла progress.php который будет иметь значение от 1 до 100 в зависимости от процесса загрузки файла.  Я также обновляю div «status» соответствующим значением.  Если текущий процент ниже 100, то я вызываю встроенную в JavaScript функцию setTimeout() чтобы через 1 секунду отправить другой запрос на обновление (вы можете изменить это значение соответствующим образом), в противном случае я снова скрываю индикатор выполнения и устанавливаю статус в «Выполнено.» 
  Функция startUpload() делает панель загрузки видимой и отправляет запрос на обновление после задержки в 1 секунду.  Эта небольшая задержка необходима для того, чтобы начать загрузку. 
  Последняя функция — это startUpload() анонимная функция, которая регистрирует startUpload() с событием отправки формы. 
Прогресс в реальном времени
  Последний файл, который объединяет все, — это файл progress.php : 
 <?php session_start(); $key = ini_get("session.upload_progress.prefix") . "myForm"; if (!empty($_SESSION[$key])) { $current = $_SESSION[$key]["bytes_processed"]; $total = $_SESSION[$key]["content_length"]; echo $current < $total ? ceil($current / $total * 100) : 100; } else { echo 100; } 
Сценарий выполняет простую математическую обработку текущего количества переданных байтов, разделенного на общий размер файла, умноженного на 100 и округленного до процента.
  Информация о передаче основывается на конкатенации значения директивы session.upload_progress.prefix и скрытого значения поля session.upload_progress.name .  Поскольку моя форма прошла «myForm», ключ сеанса определяется с помощью ini_get("session.upload_progress.prefix") . "myForm"  ini_get("session.upload_progress.prefix") . "myForm" . 
Вот скриншот индикатора выполнения в действии:
Тонкая настройка поведения
  PHP предоставляет некоторые дополнительные директивы, чтобы помочь точно настроить поведение загрузки сеанса, о которой вы должны знать.  Например, session.upload_progress.cleanup , для которого по умолчанию установлено значение 1, очищает дополнительные данные сеанса сразу после завершения загрузки.  Вы должны быть осторожны, чтобы избежать потенциального состояния гонки. 
  Посмотрите еще раз на код в progress.php и вы заметите, что я проверяю, является ли $_SESSION[$key] пустым или нет, прежде чем продолжить.  Мои функции JavaScript отключаются каждую секунду до тех пор, пока результат, полученный из progress.php будет меньше 100. Если включен session.upload_progress.cleanup и мой скрипт извлекает 99% загрузки, а через 1/2 секунды загрузка завершается $_SESSION[$key] не будет существовать для следующей проверки.  Если я не учту это, тогда моя функция JavaScript может продолжать работать даже после завершения загрузки. 
  Еще две директивы: session.upload_progress.freq и session.upload_progress.min_freq которые определяют, как часто следует обновлять сеанс.  Значение freq может быть задано либо в байтах (т.е. 100), либо в процентах от общего количества байтов (т.е. 2%).  Значение min_freq дается в секундах и указывает минимальное количество секунд между обновлениями.  Очевидно, что если min_freq был настроен на обновление каждую 1 секунду, для вашего JavaScript было бы бессмысленно проверять каждые 100 миллисекунд. 
Резюме
  Теперь у вас должно быть четкое представление о том, как создать индикатор выполнения для загрузки файлов с помощью функции выполнения загрузки сеанса.  В дальнейшем я рекомендую вам поэкспериментировать с загрузкой нескольких файлов, давая возможность отменить $_SESSION[$key]["cancel_upload"] загрузку, используя $_SESSION[$key]["cancel_upload"] или любые другие идеи, которые ваш разум может придумать.  Пожалуйста, поделитесь в комментариях своим опытом и улучшениями, которые вы сделали для других, чтобы извлечь из этого пользу! 
Изображение через файл 404 / Shutterstock
