概要
時間のかかる処理(ファイル出力やバッチ処理など)の処理状況を随時送信し使用者のストレスを軽減する。また、デバッグが簡単。
処理概要
- PHPでHTMLの<head>をブラウザに送信
- ブラウザでHTMLの<head>に記述されたスクロール処理を永久ループ開始
- PHPの処理状況をブラウザに随時送信
- ブラウザ側で<body>内の行が増え自動的に最新状況へスクロール
- PHPの出力が終了するとブラウザでDOMContentLoadedが呼ばれる
- DOMContentLoaded内にてスクロール処理の停止(フラグ)
- DOMContentLoaded内にてダウンロード用の<a>タグをクリック
- ファイルのダウンロードリクエストを行う
- ブラウザのファイル保存ダイアログが表示される
サンプルプログラムのキャプチャ
Qiita記事埋め込み用 pic.twitter.com/v6ZTTLf6UU
— やみ姐さん🔪 (@Yamine1San) July 15, 2021
サンプルプログラムが動作する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);