PHP 輸出並非直接發送給瀏覽器,而是經過層層緩衝。理解 output_buffering 機制對於控制輸出順序、實現靜態化、異步輸出、避免「Headers already sent」至關重要。
echo/print_r
→ PHP output_buffering (ob_* 函數控制)
→ Web Server Buffer (Apache/Nginx)
→ Browser Buffer
→ Browser Display
PHP 層緩衝由 php.ini output_buffering 控制:
Off (默認 CLI):無緩衝,直接交給 SAPIOn / 4096:啟用內部緩衝,達到閾值或腳本結束時刷新| 函數 | 作用 | 關鍵行為 | 典型用途 |
|---|---|---|---|
| ob_start() | 開啟/巢狀開啟輸出緩衝 | 可傳遞 callback 處理緩衝內容;巢狀調用疊加層級 | 捕獲輸出、靜態頁生成、壓縮輸出 |
| ob_get_contents() | 獲取當前緩衝內容(不清空) | 返回字符串,緩衝保持不變 | 多次使用同一緩衝內容 |
| ob_get_clean() | 獲取並清空當前緩衝 | 等同 ob_get_contents() + ob_end_clean() | 捕獲後不再輸出 |
| ob_end_clean() | 清空並關閉當前緩衝 | 丟棄內容,關閉層級 | 條件判斷決定是否輸出 |
| ob_clean() | 僅清空緩衝,不關閉 | 保持緩衝層級開啟 | 重置緩衝內容 |
| ob_flush() | 發送緩衝內容到 SAPI 並清空 | 將內容推送到 Web Server 層 | 長耗時腳本分批輸出進度 |
| flush() | 刷新 Web Server 緩衝到客戶端 | 系統調用,受 Web Server 模塊影響 (mod_gzip 等可能阻擋) | 配合 ob_flush() 實現實時輸出 |
| ob_get_flush() | 獲取並發送緩衝,然後關閉 | ob_get_contents() + ob_flush() + ob_end_flush() | 一次性獲取並輸出 |
ob_end_flush(); // 關閉 PHP 內部緩衝(若 php.ini 已開啟)
for ($i = 1; $i <= 10; $i++) {
echo "第 $i 次輸出
\n";
ob_flush(); // 1. 推送到 Web Server
flush(); // 2. 推送到瀏覽器
sleep(1);
}
順序固定:先 ob_flush() 後 flush()。反向無效。
print_r(ob_get_contents()); ob_clean(); → 無輸出。因 print_r 寫入緩衝,隨後 ob_clean() 清空。修正:ob_flush() 後再 ob_clean()mod_deflate/mod_gzip 壓縮導致 flush() 不即時生效 → 關閉壓縮或使用 zlib.output_compression = Offfastcgi_buffering on 預設緩衝 FastCGI 響應 → 配置 fastcgi_buffering off; 或 X-Accel-Buffering: noob_start();
include 'template/article.php'; // 包含大量 echo
$html = ob_get_clean();
file_put_contents('public/article/123.html', $html);
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="large.csv"');
$handle = fopen('large.csv', 'r');
while (!feof($handle)) {
echo fread($handle, 8192);
ob_flush();
flush();
}
fclose($handle);
ob_start();
// 複雜邏輯生成內容
$content = ob_get_contents();
if ($shouldOutput) {
ob_end_flush();
} else {
ob_end_clean(); // 靜默丟棄
// 記錄日誌、返回 JSON 等
}
ob_start() callback 現在接收 string 而非 arrayPHP_OUTPUT_HANDLER_* 狀態標識更精確