LoginSignup
1
1

More than 1 year has passed since last update.

PHPで処理の進捗状況の表示と、処理終了時に自動でダウンロードさせる処理のサンプル

Last updated at Posted at 2021-07-15

概要

時間のかかる処理(ファイル出力やバッチ処理など)の処理状況を随時送信し使用者のストレスを軽減する。また、デバッグが簡単。

処理概要

  1. PHPでHTMLの<head>をブラウザに送信
  2. ブラウザでHTMLの<head>に記述されたスクロール処理を永久ループ開始
  3. PHPの処理状況をブラウザに随時送信
  4. ブラウザ側で<body>内の行が増え自動的に最新状況へスクロール
  5. PHPの出力が終了するとブラウザでDOMContentLoadedが呼ばれる
  6. DOMContentLoaded内にてスクロール処理の停止(フラグ)
  7. DOMContentLoaded内にてダウンロード用の<a>タグをクリック
  8. ファイルのダウンロードリクエストを行う
  9. ブラウザのファイル保存ダイアログが表示される

サンプルプログラムのキャプチャ

サンプルプログラムが動作するURL

サンプルプログラムが動作するURL
※私のパソコンが起動していると開ける

サンプルプログラム

処理概要1~7の処理

echo_flush_download.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>処理の進捗状況の表示と完了後のダウンロードサンプル</title>
  <script>
      let flg_dom_content_loaded = false;

      window.addEventListener('DOMContentLoaded', function () {
          flg_dom_content_loaded = true;
          [].forEach.call(document.getElementsByClassName('onload_click'), function (x) {
              x.click();
          });
      });

      loopprocess();

      /**
       * 全コンテンツの出力が終了するまで下へスクロールし続けさせる
       */
      function loopprocess() {
          window.scrollTo(0, 1000000);
          if (true === flg_dom_content_loaded) {
              return;
          }
          setTimeout('loopprocess()', 300);
      }
  </script>
</head>
<body>
<?php

// <head>の送信
ob_flush();
flush();

function echo_flush($str) {
    echo $str;
    ob_flush();
    flush();
}

echo_flush('<h1>CSV作成処理を開始しました。</h1>');

$csv_nm = 'test.csv';
$csv_path = dirname(__FILE__).'/'.$csv_nm;
$handle = fopen($csv_path, 'w');

$start_time = microtime(true);

$total = 3280;
$limit = 100;
$last_page = ceil($total / $limit);

echo_flush(
    sprintf(
        "<div>%s: %d件の処理開始 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB</div>"
        , date('Y-m-d H:i:s')
        , $total
        , round(memory_get_usage() / pow(1024, 2), 1)
        , round(memory_get_peak_usage() / pow(1024, 2), 1)
    )
);

for ($page_no = 1; $page_no <= $last_page; $page_no++) {

    $page_start = (($page_no - 1) * $limit) + 1;
    $page_last = $page_no * $limit;
    if ($total < $page_last) $page_last = $total;

    echo_flush(
        sprintf(
            "<div>%s: ページ%d/%d レコード%d~%d/%d件のデータ取得開始 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB 合計時間 %s秒</div>"
            , date('Y-m-d H:i:s')
            , $page_no
            , $last_page
            , $page_start
            , $page_last
            , $total
            , round(memory_get_usage() / pow(1024, 2), 1)
            , round(memory_get_peak_usage() / pow(1024, 2), 1)
            , round((microtime(true) - $start_time), 2)
        )
    );

    // データ取得クエリ実行
    // $rs = find_many();
    usleep(500000);

    echo_flush(
        sprintf(
            "<div>%s: ページ%d/%d レコード%d~%d/%d件のデータ取得終了 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB 合計時間 %s秒</div>"
            , date('Y-m-d H:i:s')
            , $page_no
            , $last_page
            , $page_start
            , $page_last
            , $total
            , round(memory_get_usage() / pow(1024, 2), 1)
            , round(memory_get_peak_usage() / pow(1024, 2), 1)
            , round((microtime(true) - $start_time), 2)
        )
    );

    // ランダムでエラー出力
    mt_srand();
    if (0 === mt_rand(0, 8)) {
      $error_msg = date('Y-m-d H:i:s').': '.$page_start.'行目でエラーが発生しました。 カラム〇○の値が未設定のためスキップしました。';
      echo_flush('<div style="font-weight: bold;color:red;">'.$error_msg.'</div>');
    }

    // CSV追加
    // foreach ($rs as $r) {
    //    fputcsv();
    // }

    fputs($handle,
        mb_convert_encoding(sprintf(
            "%s: ページ%d/%d レコード%d~%d/%d件のデータの書き込み 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB 合計時間 %s秒"
            , date('Y-m-d H:i:s')
            , $page_no
            , $last_page
            , $page_start
            , $page_last
            , $total
            , round(memory_get_usage() / pow(1024, 2), 1)
            , round(memory_get_peak_usage() / pow(1024, 2), 1)
            , round((microtime(true) - $start_time), 2)
        ), 'SJIS-win', 'UTF-8')."\r\n"
    );
}

fclose($handle);

echo_flush(
    sprintf(
        "<div>%s: ページ%d/%d レコード%d件の処理終了 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB 合計時間 %s秒</div>"
        , date('Y-m-d H:i:s')
        , $page_no
        , $last_page
        , $total
        , round(memory_get_usage() / pow(1024, 2), 1)
        , round(memory_get_peak_usage() / pow(1024, 2), 1)
        , round((microtime(true) - $start_time), 2)
    )
);

echo_flush('<h2>CSVファイルダウンロードリンク:<a href="./echo_flush_download_file.php?file='.$csv_nm.'" class="onload_click" download="CSVファイル.csv">CSVファイル.csv</a></h2>');

echo_flush('<h1>CSV作成処理を終了しました。</h1>');

処理概要8の処理

echo_flush_download_file.php
<?php

// TODO CSVを出力した人によるダウンロードリクエストなのか要確認
$csv_path = dirname(__FILE__).'/test.csv';

header("Content-Disposition: attachment; filename=\"CSVファイル.csv\"");
header('Content-Length: '.filesize($csv_path));
header('Content-Type: application/force-download;');
readfile($csv_path);
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1