Показать прогресс для долго работающего PHP-скрипта - PullRequest
43 голосов
/ 13 августа 2011

У меня есть PHP-скрипт, который может занять не менее 10 секунд.Я хотел бы показать прогресс для него для пользователя.

В классе выполнения у меня есть свойство $progress, которое обновляется с прогрессом (в 1-100), и метод get_progress() (какая цель должна быть очевидна).

Вопрос в том, как обновить элемент <progress> на внешнем интерфейсе, чтобы пользователь мог его видеть?просто не могу обдумать это.Я не могу добраться до того же экземпляра объекта.

Ответы [ 8 ]

52 голосов
/ 17 марта 2012

Я поставлю это здесь как ссылку для всех, кто ищет - это вообще не использует javascript ..

<?php

/**
 * Quick and easy progress script
 * The script will slow iterate through an array and display progress as it goes.
 */

#First progress
$array1  = array(2, 4, 56, 3, 3);
$current = 0;

foreach ($array1 as $element) {
    $current++;
    outputProgress($current, count($array1));
}
echo "<br>";

#Second progress
$array2  = array(2, 4, 66, 54);
$current = 0;

foreach ($array2 as $element) {
    $current++;
    outputProgress($current, count($array2));
}

/**
 * Output span with progress.
 *
 * @param $current integer Current progress out of total
 * @param $total   integer Total steps required to complete
 */
function outputProgress($current, $total) {
    echo "<span style='position: absolute;z-index:$current;background:#FFF;'>" . round($current / $total * 100) . "% </span>";
    myFlush();
    sleep(1);
}

/**
 * Flush output buffer
 */
function myFlush() {
    echo(str_repeat(' ', 256));
    if (@ob_get_contents()) {
        @ob_end_flush();
    }
    flush();
}

?>
42 голосов
/ 22 ноября 2014

Если ваша задача состоит в том, чтобы загрузить огромный набор данных или обработать его на сервере, при обновлении прогресса на сервере вам следует рассмотреть возможность использования какой-либо архитектуры заданий, при которой вы запускаете задание и выполняете его с помощью другого сценария. запуск на сервере (например, масштабирование / обработка изображений и т. д.). При этом вы делаете одну вещь за раз, таким образом формируя конвейер задач, где есть входные данные и конечный обработанный выходной результат.

На каждом шаге конвейера состояние задачи обновляется внутри базы данных, которая затем может быть отправлена ​​пользователю любым механизмом серверного проталкивания, существующим в наши дни. Запуск одного скрипта, который обрабатывает загрузки и обновления, увеличивает нагрузку на ваш сервер, а также ограничивает вас (что если браузер закроется, что если произойдет какая-то другая ошибка). Когда процесс разделен на этапы, вы можете возобновить невыполненную задачу с того места, где она была выполнена последней.

Существует много способов сделать это. Но общий процесс выглядит следующим образом

enter image description here

Следующий метод - это то, что я сделал для личного проекта, и этот скрипт был хорош для загрузки и обработки тысяч изображений высокого разрешения на мой сервер, которые затем были уменьшены до нескольких версий и загружены в amazon s3 при распознавании объектов внутри них. (Мой оригинальный код был на python)

Шаг 1:

Инициировать перенос или задачу

Сначала загрузите свой контент, а затем сразу же верните идентификатор транзакции или uuid для этой транзакции с помощью простого запроса POST. Если вы выполняете в задаче несколько файлов или несколько вещей, вы можете также обработать эту логику на этом шаге

Шаг 2:

Сделай работу и верни прогресс.

Как только вы выяснили, как происходят транзакции, вы можете использовать любую технологию push на стороне сервера для отправки пакета обновления. Я бы выбрал WebSocket или Server Sent Events, в зависимости от того, что применимо, к «длинному опросу» в неподдерживаемых браузерах. Простой метод SSE будет выглядеть следующим образом.

function TrackProgress(upload_id){

    var progress = document.getElementById(upload_id);
    var source = new EventSource('/status/task/' + upload_id );

    source.onmessage = function (event) {
        var data = getData(event); // your custom method to get data, i am just using json here
        progress.setAttribute('value', data.filesDone );
        progress.setAttribute('max', data.filesTotal );
        progress.setAttribute('min', 0);
    };
}

request.post("/me/photos",{
    files: files
}).then(function(data){
     return data.upload_id;
}).then(TrackProgress);

На стороне сервера вам нужно будет создать что-то, что будет отслеживать задачи, достаточно простой архитектуры Jobs с job_id и прогрессом, отправляемым в базу данных. Я бы оставил планирование работы вам и маршрутизацию, но после этого концептуальный код (для простейшего SSE, которого будет достаточно вышеуказанного кода) выглядит следующим образом.

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
/* Other code to take care of how do you find how many files are left 
   this is really not required */
function sendStatusViaSSE($task_id){
    $status = getStatus($task_id);
    $json_payload = array('filesDone' => $status.files_done,
                          'filesTotal' => $status.files_total);
    echo 'data: ' . json_encode( $json_payload ) . '\n\n';
    ob_flush();
    flush();

    // End of the game
    if( $status.done ){
        die();
    }

}

while( True ){
     sendStatusViaSSE( $request.$task_id );
     sleep(4);
}

?>

Хороший учебник по SSE можно найти здесь http://html5doctor.com/server-sent-events/

и вы можете узнать больше о загрузке обновлений с сервера по этому вопросу Отправка обновлений с сервера

Выше было концептуальное объяснение, есть и другие способы достичь этого, но это было решение, которое позаботилось о довольно большой задаче в моем случае.

25 голосов
/ 13 августа 2011

Это довольно сложно, (FYI) PHP-процесс и ваш AJAX-запрос обрабатываются отдельным потоком, поэтому вы не можете получить значение $progress.

Быстрое решение: вы можете записать значение прогресса в $_SESSION['some_progress'] каждый раз, когда оно обновляется, тогда ваш AJAX-запрос может получить значение прогресса, открыв $_SESSION['some_progress'].

Вам понадобится JavaScript setInterval() или setTimeout(), чтобы продолжать вызывать обработчик AJAX, пока вы не получите возврат как 100.

Это не идеальное решение, но быстрое и простое.


Поскольку вы не можете использовать один и тот же сеанс дважды одновременно, используйте вместо этого базу данных. Запишите состояние в базу данных и прочитайте его с интервалом вызова AJAX.

12 голосов
/ 10 июня 2014

Это старый вопрос, но у меня была похожая потребность.Я хотел запустить скрипт с командой php system() и показать вывод.

Я сделал это без опроса.

Для второго случая Rikudoit должно быть что-то вроде этого:

JavaScript

document.getElementById("formatRaid").onclick=function(){
    var xhr = new XMLHttpRequest();     
    xhr.addEventListener("progress", function(evt) {
        var lines = evt.currentTarget.response.split("\n");
        if(lines.length)
           var progress = lines[lines.length-1];
        else
            var progress = 0;
        document.getElementById("progress").innerHTML = progress;
    }, false);
    xhr.open('POST', "getProgress.php", true);
    xhr.send();
}

PHP

<?php 
header('Content-Type: application/octet-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.

// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Clear, and turn off output buffering
while (ob_get_level() > 0) {
    // Get the curent level
    $level = ob_get_level();
    // End the buffering
    ob_end_clean();
    // If the current level has not changed, abort
    if (ob_get_level() == $level) break;
}   

while($progress < 100) {
    // STUFF TO DO...
    echo '\n' . $progress;
}
?>
7 голосов
/ 07 марта 2013

Решения:

  1. Опрос Ajax - На стороне сервера сохраните где-нибудь прогресс и затем используйте ajax-вызов для получения прогресса через регулярные промежутки времени.

  2. События, отправленные сервером - функция html5, которая позволяет генерировать события dom из результатов, отправленных сервером. Это лучшее решение для такого случая, но IE 10 не поддерживает его.

  3. Потоковая передача сценария / Iframe - используйте iframe для потоковой передачи выходных данных из сценария длительного выполнения, который будет выводить теги сценария в виде интервалов, которые могут вызвать некоторую реакцию в браузере.

6 голосов
/ 12 июля 2016

Здесь я просто хотел добавить две проблемы в дополнение к тому, что @Jarod Law написал выше https://stackoverflow.com/a/7049321/2012407

Очень просто и эффективно. Я подправил и использовал :) Итак, мои 2 проблемы:

  1. вместо использования setInterval() или setTimeout() используйте рекурсивный вызов в обратном вызове, например:

    function trackProgress()
    {
        $.getJSON(window.location, 'ajaxTrackProgress=1', function(response)
        {
            var progress = response.progress;
            $('#progress').text(progress);
    
            if (progress < 100) trackProgress();// You can add a delay here if you want it is not risky then.
        });
    }
    

    Поскольку вызовы асинхронны, в противном случае они могут возвращаться в нежелательном порядке.

  2. сохранение в $_SESSION['some_progress'] - это разумно, и вам не нужно хранить базу данных. Что вам действительно нужно, так это разрешить вызов обоих скриптов одновременно, а не ставить их в очередь PHP. Так что вам нужно больше всего session_write_close()! Я разместил очень простой демонстрационный пример здесь: https://stackoverflow.com/a/38334673/2012407

4 голосов
/ 13 августа 2011

Рассматривали ли вы вывод javascript и использование потокового сброса? Это будет выглядеть примерно так

echo '<script type="text/javascript> update_progress('.($progress->get_progress()).');</script>';
flush();

Этот вывод немедленно отправляется в браузер из-за сброса. Делайте это периодически из длинного скрипта, и он должен прекрасно работать.

0 голосов
/ 22 ноября 2014

Часто в веб-приложениях у нас может быть запрос к серверной системе, который может вызвать длительный процесс, такой как поиск огромного количества данных или длительный процесс в базе данных. Затем веб-страница интерфейса может зависнуть и дождаться завершения процесса. Во время этого процесса, если мы сможем предоставить пользователю некоторую информацию о ходе выполнения внутреннего процесса, это может улучшить пользовательский опыт. К сожалению, в веб-приложениях это кажется непростой задачей, поскольку языки веб-сценариев не поддерживают многопоточность, а HTTP не поддерживает состояния. Теперь у нас есть AJAX для симуляции процесса в реальном времени.

В основном нам нужно три файла для обработки запроса. Первый - это скрипт, который запускает реальное долго выполняемое задание, и для хранения прогресса в нем должна быть переменная сеанса. Второй сценарий - это сценарий состояния, который будет отображать переменную сеанса в сценарии длительного выполнения задания. Последним является клиентский скрипт AJAX, который может часто опрашивать скрипт статуса.

Для получения подробной информации о реализации, вы можете обратиться к PHP, чтобы динамически получить длительный прогресс процесса

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...