結構PHPからプログレス作るの難しいなぁ

なんとか、完成はしたんだが、思った通りに作れたわけでは無くて、対症療法で対応したと言う感じで腑に落ちない。

PHP側のob_*系を使って、finish()を色々と使い回すのだが、ローカル環境では問題無く動作するものの、サーバーに上げるとPHPの処理が全部終わって、最後に一気に吐き出す感じで、結局処理中はフリーズしたみたいになってしまい意味が無い。

これは、Apache絡みでバッファを吐き出すタイミングが意図した動きにならないので、htaccessで止めたりするとの事だが、これも上手くいかない。

当初、PHPだけでecho、finishすれば済むと思ったのだが、駄目だった。

そこで、$_SESSIONやらcookieやら試したのだが、クライアントの環境や、許可設定などで動かない可能性もあるし、もっと完璧に進捗表示が出来る仕組みが無いか探っていた。

結局Ajaxを使ってsetTimeout()仕掛けるしか無かったのだが、レスポンスを受け取る場合、結局finish()が効かなければリアルタイムに処理が出来ないので、どうしたかというと、phpからコンソールへ吐き出してレスポンスした。

これをAjaxで拾ってプログレスを動かすことに成功した。

結構邪道な方法だとは思うが、丁寧に作ってもローカル環境でしか動かず、実際にユーザ環境で動かす必要があるわけで、この際、邪道かもしれないがとりあえず完了した。

一往、別ファイルのPHPを動かして動かしたかったのであえて分離してAjaxでレスポンスを受け取る様にした。

ちゃんと学校で勉強した人から見ると邪道だと思うが、独学PGなんでそこは割り引いてみて欲しい。(動けば良いのさ。金になるんだから)

ちなみにbootstrap4を使っているがhtml5のプログレスでもOKだ。

xhr.responseTextにコンソールに吐き出したカウントがとれるのでそれを利用すれば良い。

ただし、送信する前に、ループのカウントを取得する必要がある書き方をしている。(変数dの事)

ここもphpからレスポンスで返す様にすれば不要かもしれないが、これはファイルアップロードの際、実際にファイルを送信せず、縮小したBLOBデータだけをDBに保存して、モバイルでも高速に写真を送信する仕組みで使っているので、ファイルアップロード用のプログレス処理が使えず苦慮した。

ファイルアップロードだと超簡単にhtml5だけでもプログレス出来るのだが、実態のファイルをアップするとモバイルだとパケットも節約出来ないし時間がかかる。

そこで、使用する写真の解像度は320×200程度のサイズで良いので、iPadなんかで撮影した画像は超高解像度で、送信するにも縮小してファイル名変更してなんて事が簡単に出来ない。

なので、アップロードの仕組みで自動的に縮小して、BLOBデータだけを送信すれば、あっという間に送信完了して、次の手続き処理が出来るのだが、そういう時のプログレス処理が必要で、なぜここまで拘ったかと言うと、実際にiPadでやってみると、なにかエラーが表示されて、このエラーがなかなか回避できない。

調べてみるとSafariのバージョンを上げるとかあるし、Chromeをインストールしても10枚以上送信するとなぜかエラーする。

PCだと300枚一気に送信してもエラーしないのだがAndroidでも4,50枚以上送信するとエラーしたり不安定になる。

これは、どうもPHPが動いている間、何か通信が切れてしまうのか、タイムアウトを調整してもエラーが回避できないので、画面を動かしておけばなんとかなるだろうと言うもくろみでやっている。

一往、テストではいけそうなので、明日実際に導入してみて確認だ。

上手くいくと良いのだが...

view側

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>PHPプログレスサンプル</title>
    <script src="./js/jquery-3.3.1.min.js"></script>
    <script src="./js/bootstrap.min.js"></script>
    <link href="./css/bootstrap.min.css" rel="stylesheet">
<script type="text/javascript">
        function prog(){
            alert("プログレスバー動作テスト");
            $(".progress").show();
            $("#title").show();

            var d=100;            
            var c=1;

            console.log('start');
            $.ajax({
                type: 'post',
                url: './progress.php',
                xhrFields: {
                    onloadstart: function() {
                        var xhr = this;
                        $.mytimer = setInterval(function() {
                        console.log(xhr.responseText);
                        var cnt=xhr.responseText;
                            if(cnt<d+1){
                                var status = parseInt((cnt*10)*(100/d)) + "%";
                                $("#progress_elem").css({width: status}).text(status);
                            }else{
                                alert("お疲れ様でした。アップロード完了です。");
                                $("#progress_elem").css({width: "100%"}).text("100%");
                                $(".progress").hide();
                                $("#title").hide();
                                $("#progress_elem").css({width: "0%"}).text("0%");
                                setTimeout('clearInterval($.mytimer)', 1000);
                            }
                            c=c+1;
                        }, 1000);
                    }
                },
                success: function() {
                    console.log('finished!');
                    // すぐにクリアしてしまうと最終的なレスポンスに対する処理ができないので
                    // タイマーと同じ間隔を空けてクリアする必要がある
                    //clearInterval($.mytimer);
                    $("#progress_elem").css({width: "100%"}).text("100%");
                    setTimeout('clearInterval($.mytimer)', 1000);
                }
            });
        }
</script>
</head>
    <body>
        <button type="button" name="hint" onclick="prog(this.form);">
        ボタンを押すと<strong>プログレスバーテスト</strong>を開始
        </button>
        <h2 id="title" style="display:none;">送信中</h2>
        <div class="progress" style="margin:auto;width:80%;height:30px;display:none;">
            <div id="progress_elem" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">0%</div>
        </div>
    </body>
</html>

php側

<?php
    set_time_limit(120);
    echo str_pad(" ",4096)."<br />\n";
    ob_end_flush();
    ob_start('mb_output_handler');
    flush();
        for ( $i = 1; $i <= 100; $i++ ) {

            echo $i."\n";

            ob_flush();
            flush();

            //sleep( 1 );	// 時間がかかる処理
        }
        ob_end_flush();
        exit;
?>

動作サンプル

やはり思ったとおり、iPadでのエラーは解消出来た。