web-dev-qa-db-ja.com

長時間実行の進行状況を表示PHPスクリプト

PHPスクリプトは実行に少なくとも10秒かかる可能性があります。ユーザーの進行状況を表示したいと思います。

実行中のクラスには、進行状況(1〜100)で更新される_$progress_プロパティと、get_progress()メソッド(目的は明らかです)があります。

問題は、ユーザーが見るためにフロントエンドの_<progress>_要素を更新する方法ですか?

私はAJAXが解決策であると思うが、私はそれを回避することができない。同じオブジェクトインスタンスに到達できない。

43
Madara Uchiha

タスクが巨大なデータセットをアップロードするか、サーバーで処理する場合、サーバーへの進行状況を更新しながら、ジョブを開始し、他のスクリプトで実行するジョブアーキテクチャを使用することを検討する必要がありますサーバー(たとえば、画像のスケーリング/処理など)。これでは、一度に1つのことを行うため、入力と最終処理済み出力があるタスクのパイプラインが形成されます。

パイプラインの各ステップで、タスクのステータスはデータベース内で更新され、最近存在するサーバープッシュメカニズムによってユーザーに送信できます。アップロードと更新を処理する単一のスクリプトを実行すると、サーバーに負荷がかかり、制限されます(ブラウザーが閉じた場合、他のエラーが発生した場合)。プロセスを複数のステップに分割すると、失敗したタスクを最後に成功したポイントから再開できます。

それを行うには多くの方法があります。しかし、全体的なプロセスフローは次のようになります

enter image description here

次の方法は、私が個人プロジェクトで行ったことであり、このスクリプトは、数千の高解像度画像をサーバーにアップロードして処理し、複数のバージョンにスケールダウンして、その中のオブジェクトを認識しながらAmazon s3にアップロードするのに適しています。 (元のコードはpythonにありました)

ステップ1 :

トランスポートまたはタスクを開始する

最初にコンテンツをアップロードし、単純なPOST=リクエストを使用して、このトランザクションのトランザクションIDまたはUUIDをすぐに返します。タスクで複数のファイルまたは複数のことを実行している場合、このステップのロジック

ステップ2:

仕事をして、進行状況を返します。

トランザクションの発生方法がわかったら、サーバー側のプッシュテクノロジーを使用して更新パケットを送信できます。サポートされていないブラウザでは、ロングポーリングにフォールバック可能な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);

サーバー側では、タスクを追跡する何かを作成する必要があります。job_idとdbに送信される進行状況を備えた単純なJobsアーキテクチャで十分です。ジョブスケジューリングはあなたとルーティングにも任せますが、その後の概念コード(上記のコードで十分な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/

この質問に関するサーバーからの更新のプッシュについて詳しく読むことができます サーバーからの更新のプッシュ

上記は概念的な説明であり、これを達成する他の方法もありますが、これは私の場合、かなり大きなタスクを処理するソリューションでした。

42
ShrekOverflow

これを検索する人のためのリファレンスとしてここに置きます-これは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();
}

?>
52
l0ft13

(FYI)PHPプロセスとAJAX要求は別のスレッドで処理されているため、_$progress_を取得できません値。

簡単な解決策:更新されるたびに進捗値を_$_SESSION['some_progress']_に書き込むことができ、その後AJAXリクエストは_$_SESSION['some_progress']_にアクセスして進捗値を取得できます。

_100_として返されるまで、AJAXハンドラー)を呼び出し続けるには、JavaScriptのsetInterval()またはsetTimeout()が必要です。

これは完璧なソリューションではありませんが、迅速かつ簡単です。


同じセッションを同時に2回使用することはできないため、代わりにデータベースを使用してください。ステータスをデータベースに書き込み、interval'd AJAX呼び出しでデータベースから読み取ります。

26
Jarod DY Law

それは古い質問ですが、私には同様のニーズがありました。 php system()コマンドを使用してスクリプトを実行し、出力を表示したかったのです。

私はポーリングなしでそれをやった。

2番目の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;
}
?>
13
Ramon La Pietra

ソリューションは次のとおりです。

  1. Ajaxポーリング-サーバー側で進行状況をどこかに保存し、次にajax呼び出しを使用して定期的に進行状況を取得します。

  2. サーバー送信イベント-サーバーから送信された出力からdomイベントを生成できるhtml5機能。これはそのような場合に最適なソリューションですが、IE 10はサポートしていません。

  3. スクリプト/ iframeストリーミング-iframeを使用して、ブラウザで何らかの反応を生成できる間隔としてスクリプトタグを出力する長時間実行スクリプトからの出力をストリーミングします。

7
Silver Moon

ここでは、上記の@Jarod Lawが書いたことに加えて、2つの懸念を追加したかっただけです 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

6
antoni

JavaScriptの出力とストリームフラッシュの使用を検討しましたか?こんな感じ

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

この出力は、フラッシュのためにすぐにブラウザに送信されます。実行時間の長いスクリプトから定期的に実行すると、美しく動作するはずです。

4
Michael Petrov

多くの場合、Webアプリケーションでは、大量のデータの検索や長時間実行されるデータベースプロセスなど、長時間実行プロセスをトリガーするバックエンドシステムへの要求があります。その後、フロントエンドWebページがハングし、プロセスが終了するのを待つ場合があります。このプロセス中に、バックエンドプロセスの進行状況に関する情報をユーザーに提供できれば、ユーザーエクスペリエンスが向上する可能性があります。残念ながら、Webアプリケーションでは、Webスクリプト言語はマルチスレッドをサポートせず、HTTPはステートレスであるため、これは簡単なタスクではないようです。リアルタイムプロセスをシミュレートするためにAJAXを使用できるようになりました。

基本的に、リクエストを処理するには3つのファイルが必要です。最初のスクリプトは、実際に長時間実行されるジョブを実行するスクリプトであり、進行状況を保存するセッション変数が必要です。 2番目のスクリプトは、長時間実行されるジョブスクリプトのセッション変数をエコーするステータススクリプトです。最後の1つは、クライアント側AJAXで、頻繁にステータススクリプトをポーリングできるスクリプトです。

実装の詳細については、 PHPを使用すると、長時間実行されるプロセスの進行状況を動的に取得できます を参照できます。

0
PixelsTech