輸出緩衡區是 PHP 在將內容發送給 Web 伺服器(Apache/Nginx)前,在記憶體中暫存輸出數據的機制。它解決了「輸出內容後無法發送 Header」的經典問題,並支援靜態快取、分段輸出大文件、Gzip 壓縮等高級用法。
PHP 腳本
↓ echo/print/var_dump 等輸出語句
PHP 內部輸出緩衝區 (ob_* 系列控制)
↓ ob_flush() / 隱式刷新 / 腳本結束
Web 伺服器緩衝區 (Apache/Nginx)
↓ flush() / 隱式刷新 / 響應結束
瀏覽器
兩層緩衝區:PHP 層(ob_*)與 Web 伺服器層(flush 影響)。
| 指令 | 可選值 | 預設 | 說明 |
|---|---|---|---|
output_buffering | On / Off / 整數(KB) | 4096 (4KB) | PHP 內部緩衝區大小。On=無限,整數=超過自動刷新 |
implicit_flush | On / Off | Off | 每次 PHP 輸出是否自動調用 flush() 刷新 Web 伺服器緩衝區 |
建議:生產環境設 output_buffering = 4096,開發環境可設 Off 便於即時看到輸出。
| 函數 | 作用 | 返回值 | 常用場景 |
|---|---|---|---|
ob_start(callable $callback?, int $chunk_size=0, int $flags=PHP_OUTPUT_HANDLER_STDFLAGS) | 開啟輸出緩衝區,可指定回調處理輸出 | bool | 啟動快取、Gzip、字符替換 |
ob_get_contents() | 獲取緩衝區內容(不清除) | string|false | 靜態快取、捕獲模板輸出 |
ob_get_clean() | 獲取內容並關閉緩衝區 | string | 一次性捕獲並結束 |
ob_get_flush() | 獲取內容、發送到 Web 伺服器、關閉緩衝區 | string|false | 捕獲並立即輸出 |
ob_end_flush() | 發送緩衝區內容到 Web 伺服器並關閉緩衝區 | bool | 正常結束輸出 |
ob_end_clean() | 丟棄緩衝區內容並關閉緩衝區 | bool | 錯誤處理、重定向前清理 |
ob_flush() | 發送 PHP 緩衝區內容到 Web 伺服器(不關閉) | bool | 長連接、流式輸出、進度條 |
flush() | 發送 Web 伺服器緩衝區到瀏覽器 | void | 配合 ob_flush() 實現即時輸出 |
ob_clean() | 清空 PHP 緩衝區(不關閉) | void | 重新生成輸出前清理 |
ob_get_length() | 獲取緩衝區內容長度 | int|false | 判斷是否有輸出 |
ob_list_handlers() | 獲取當前活躍的回調處理器名稱 | array | 調試、檢查 Gzip 是否啟用 |
ob_gzhandler() | 內建 Gzip/Deflate 壓縮回調 | string|false | ob_start('ob_gzhandler') 啟用壓縮 |
ob_implicit_flush(bool $flag) | 設定/獲取隱式刷新狀態 | int | 等同 php.ini implicit_flush |
// 正確:先推送到 Web 伺服器,再推送到瀏覽器
ob_flush();
flush();
// 錯誤:Web 伺服器緩衝區為空,flush() 無效
flush();
ob_flush();
<?php
ob_start(); // 開啟緩衝,所有 echo 暫存
echo "歡迎訪問"; // 實際未輸出到瀏覽器
header('Location: /login.php'); // 可以正常發送 Header
ob_end_flush(); // 最後統一輸出
<?php
$file = '/path/to/large_video.mp4';
$size = filesize($file);
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header('Content-Length: ' . $size);
header('Accept-Ranges: bytes');
ob_end_clean(); // 確保無殘留輸出
$fp = fopen($file, 'rb');
while (!feof($fp)) {
echo fread($fp, 8192); // 每次 8KB
ob_flush();
flush(); // 立即推送到瀏覽器
}
fclose($fp);
關鍵:分塊讀取 + ob_flush + flush,PHP 記憶體佔用恆定在 8KB,不受 memory_limit 限制。
<?php
$cacheFile = "cache/page_{$id}.html";
$expire = 3600;
if (file_exists($cacheFile) && time() - filemtime($cacheFile) < $expire) {
readfile($cacheFile);
exit;
}
ob_start();
// 複雜邏輯、資料庫查詢、模板渲染...
$content = ob_get_contents();
file_put_contents($cacheFile, $content);
ob_end_flush();
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // 關閉 Nginx 緩衝
ob_implicit_flush(true);
ob_end_flush();
for ($i = 1; $i <= 100; $i++) {
echo "data: 進度 $i%\n\n";
ob_flush();
flush();
sleep(1);
}
<?php
// 方式一:php.ini 開啟 zlib.output_compression = On
// 方式二:代碼控制
if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
ob_start('ob_gzhandler');
} else {
ob_start();
}
// 後續輸出自動壓縮
| 組件 | 行為 | 對策 |
|---|---|---|
| 舊版 IE | 接收 < 256 字節不顯示 | 補充空白:echo str_repeat(' ', 1024); |
| Nginx fastcgi_buffering | 預設 4KB/8KB 才刷新 | fastcgi_buffering off; 或 header('X-Accel-Buffering: no'); |
| Apache mod_gzip / mod_deflate | 有獨立緩衝區,flush 可能無效 | 關閉模組或 SetEnv no-gzip dont-vary |
| CDN/代理 | 可能緩存響應 | 設定 Cache-Control: no-cache, must-revalidate |
ob_start() 必須對應 ob_end_flush() 或 ob_end_clean(),否則緩衝區洩漏導致輸出重複/缺失。ob_get_contents() 在 ob_end_clean() 後調用返回空字串。session_write_close():長時間流式輸出時,Session 檔案鎖會阻塞同用戶其他請求。輸出緩衝區是 PHP 從「模板引擎」進化為「現代 Web 框架基礎設施」的關鍵技術。掌握 雙層緩衝架構、ob_flush/flush 順序、分塊輸出突破記憶體限制、回調處理器,能解決 Header 錯誤、大文件下載、即時推送、靜態快取、Gzip 壓縮等核心問題。記住:輸出控制權在你手中,不要讓緩衝區控制你。