PHP出力バッファで進捗管理

∠( ゚Д゚)/イェェガァァ

どうもメガネです。

実行中のプログラムの進捗をクライアントで確認できるようにする仕組みをPHPで作成してみました。
とても簡易的なものなので汎用性とかあんま考えてないですw
最近ではフレームワークを使えば簡単に実装できる機能ではありますが、APCを使ってたり、細かいカスタマイズができそうにないので自作することにしました。
今回はリクエスト数を減らすという目的だけで ob_start() と ob_flush() を使って処理を書きました。

ob_start ob_flushってなんぞ?

出力されるはずのデータを一旦内部バッファにためこむことができる関数。

何が便利なの?

これを使うと、echoで出力したデータを変数に代入したり、後の条件で出力させなかったりできます

例えば、以下のファイルのバッファリングをコントロールしてみます。

出力バッファリング無

1
2
3
4
<?php
echo 'Hello ' . $test_text . '!';

$test_text = 'world'; // テンプレート内で使う変数

出力結果:

1
Hello !

出力バッファリング有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
ob_start(); // 自動出力無効

echo 'Hello ' . $test_text . '!';

$test_text = 'world'; // テンプレート内で使う変数

$body = ob_get_contents(); // 出力内容を変数にいれる

// 本来の出力内容(3行目のechoによる出力)を削除
// ※これやらないと二回出力されちゃう
ob_end_clean();

echo $body; // 出力

出力結果:

1
Hello world!

出力のタイミングをプログラムで制御することが可能になる。
今回はこの関数を使って出力バッファを断片的にクライアントに送信して、結果を逐一jsで処理して進捗状況をブラウザに表示してやろうぜってとこまでやってみる。

javascriptで進捗状況のイベントをハンドリングする

ファイル名: index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<!-- フォームの送信先をiframe宛てにし、iframe内のからwindow.ProgressBarが呼ばれる -->
<form action="progress.php" enctype="multipart/form-data" method="post" target="progress">
<input id="file1" type="file" name="file1" />
<input type="submit" value="upload" />
<span id="progress">0%</span>
</form>

<iframe style="display: none;" name="progress" height="240" width="320"></iframe>

<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script>
var progress = document.getElementById('progress');
window.ProgressBar = {
// 進捗が更新されるたびに実行したいメソッド
update: function (percent) {
progress.text(percent + '%');
},
// 完了時に実行したいメソッド
successe: function () {
},
// 失敗時に実行したいメソッド
error: function (error_message) {
alert(error_message);
},
// キャンセル時に実行したいメソッド
cancel: function () {
}
};
</script>
</body>
</html>

ファイル名: progress.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<?php
try {
if (!isset($_FILES["upfile"]) || !is_uploaded_file($_FILES["upfile"]["tmp_name"])) {
throw new Exception('アップロード失敗。');
}
$handle = fopen($_FILES["upfile"]["tmp_name"], "rb+");
$proc_length = 0;
while (!feof($handle)) {
// 通信が切断されていたら処理を中断
if (connection_aborted()) {
echo '<script>'
echo 'parent.ProgressBar.cancel();';
echo '</script>';
break;
}
// ファイルの読み込み
$contents = fread($handle, 8192);
$proc_length += strlen($contents);
$data = '<script>';
$data = 'parent.ProgressBar.update(\'' . $proc_length . '\');'
$data = '</script>';
// ブラウザに応答を出力させる
flush();
ob_flush();
sleep(1);
}
} catch (Exception $e) {
echo '<script type="text/javascript">';
echo 'parent.ProgressBar.error("' . $e->getMessage() . '");';
echo '</script>';
}
fclose($handle);
?>
</body>
</html>

考察
リクエスト数は減るが、コネクションが専有されてしまうのであまり使わないほうがいいかも。
プログラムの実行状況の管理にAPCを使わないのであれば、代わりにファイルもしくはDBで進捗理するようにして、定期的にクライアントから非同期で実行状況を確認しにいくような仕組みのほうがいいですね。

 

Please share