PHP 輸出緩衝是理解「為什麼 echo 不立即顯示在瀏覽器」「如何實現頁面靜態化」「如何壓縮輸出」的關鍵。本文從底層原理(PHP buffer → TCP buffer → 瀏覽器)出發,徹底講透 output_buffering ini 設置、ob_start() 手動控制、巢狀緩衝、常見陷阱與生產級應用模式。
echo/print
→ PHP output_buffer (默認 4KB,由 output_buffering 控制)
→ SAPI 層 (cli/apache2handler/fpm)
→ TCP 發送緩衝區 (內核 socket buffer)
→ 網絡傳輸
→ 瀏覽器接收並渲染
| 值 | 行為 |
|---|---|
| Off / 0 | 禁用 PHP 級緩衝,echo 直接進 SAPI → TCP。需配合 flush() 強制發送。 |
| On / 整數 (如 4096) | 啟用,緩衝區達到指定字節數自動沖刷。默認 4096 (4KB)。 |
注意: ini_set('output_buffering', 0) 在運行時**無效**,必須在 php.ini 或 .user.ini 中設置。
ob_start(callable $callback = null, int $chunk_size = 0, int $flags = PHP_OUTPUT_HANDLER_STDFLAGS): bool
$callback:輸出處理函數,接收緩衝區內容返回處理後字符串。常用 ob_gzhandler (gzip 壓縮)、自定義替換模板變量。$chunk_size:緩衝區達到此字節數自動沖刷。0 表示無限制,腳本結束或手動沖刷前不發送(最常用模式)。$flags:PHP_OUTPUT_HANDLER_CLEANABLE (可清理)、PHP_OUTPUT_HANDLER_FLUSHABLE (可沖刷)、PHP_OUTPUT_HANDLER_REMOVABLE (可移除)。默認全開。| 函數 | 作用 | 返回值 | 緩衝區狀態 |
|---|---|---|---|
| ob_start() | 開啟新緩衝層(棧頂) | bool | 新層激活 |
| ob_get_contents() | 獲取當前層緩衝區內容副本 | string | 不變 |
| ob_get_clean() | 獲取內容並刪除當前層 | string | 層移除,內容丟棄 |
| ob_end_flush() | 沖刷並關閉當前層(發送給上層/客戶端) | bool | 層移除,內容已發送 |
| ob_end_clean() | 清空並關閉當前層(不發送) | bool | 層移除,內容丟棄 |
| ob_flush() | 沖刷當前層內容到上層/客戶端(層保留) | bool | 層保留,內容保留 |
| ob_clean() | 清空當前層內容(層保留) | void | 層保留,內容清空 |
| ob_get_level() | 獲取當前緩衝層數 | int | — |
| ob_get_status() | 獲取所有層狀態數組 | array | — |
<?php
// 1. output_buffering=4096, 少量輸出:瀏覽器等腳本結束才顯示(緩衝未滿)
// 2. output_buffering=0 + flush():間歇性輸出,實時感知
// 3. output_buffering=4096, 大量輸出(>4KB):間歇性輸出(緩衝滿自動沖刷)
// 4. ob_start() + 大量輸出:腳本結束前瀏覽器空白,結束瞬間全量顯示(緩衝無限大)
function renderTemplate(string $file, array $vars): string {
extract($vars, EXTR_SKIP);
ob_start();
include $file; // 模板文件中直接 echo 變量
return ob_get_clean(); // 獲取渲染結果,不直接輸出
}
// 控制器
$html = renderTemplate('user_profile.tpl.php', ['user' => $user]);
// 可在此處進一步處理 $html (注入 CSP nonce、壓縮、緩存)
echo $html;
ob_start();
// 複雜邏輯渲染
$content = ob_get_clean();
file_put_contents($cacheFile, $content); // 存入文件緩存
// 下次請求直接 readfile($cacheFile) 或 include
// php.ini: zlib.output_compression = On (推薦,由 SAPI 層處理)
// 或代碼控制:
if (str_contains($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', 'gzip')) {
ob_start('ob_gzhandler');
}
// 腳本結束自動壓縮發送,或 ob_end_flush()
// 關閉所有輸出緩衝
while (ob_get_level()) ob_end_clean();
// 設置頭信息
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="big.zip"');
header('Content-Length: ' . filesize($path));
// 直接輸出到 TCP,不經 PHP 緩衝
readfile($path); // 或 fopen + fread + echo 循環
ob_start() (如果啟用) 或直接發送頭部。解決:文件純 PHP 省略 ?>,編碼 UTF-8 無 BOM,邏輯前不輸出。ob_start() 必有對應 ob_end_flush() 或 ob_get_clean()。ob_flush(); flush(); 雙調用。try/finally 確保 ob_end_clean() 執行,或註冊 register_shutdown_function 兜底。輸出緩衝是 PHP 靈活輸出控制的基石。理解緩衝層棧、沖刷/清理語義、SAPI 交互邊界,能讓你從容應對模板渲染、靜態化、壓縮、流式下載、實時進度條等所有輸出場景。