PHP 輸出緩衡機制允許在腳本執行期間攔截、修改、延遲輸出內容,解決「Header 已發送無法重定向/設置 Cookie」等經典問題,並支援頁面靜態化、內容壓縮、模板渲染等高級模式。本文系統講解 ob_* 函數族、巢狀緩衝、實戰應用模式。
| 函數 | 作用 | 返回值 | 常用場景 |
|---|---|---|---|
ob_start(callable $callback=null, int $chunk_size=0, int $flags=0) | 開啟輸出緩衝 | bool | 腳本開頭、模板引擎捕獲輸出 |
ob_get_contents() | 獲取緩衝區內容 (不清空) | string|false | 模板渲染捕獲、內容檢查 |
ob_get_clean() | 獲取內容並關閉緩衝 | string|false | 一次性捕獲輸出 |
ob_get_flush() | 獲取內容、輸出並關閉 | string|false | 需同時輸出並獲取 |
ob_end_flush() | 輸出緩衝並關閉 | bool | 正常結束輸出 |
ob_end_clean() | 丟棄緩衝並關閉 | bool | 錯誤處理、條件輸出取消 |
ob_flush() | 刷新緩衝區到輸出 (不關閉) | bool | 長運行腳本分批輸出、SSE |
flush() | 刷新 PHP/SAPI 緩衝到 Web 服務器 | void | 配合 ob_flush() 實現實時輸出 |
ob_get_level() | 獲取巢狀緩衝層級 | int | 調試、條件開啟 |
ob_get_length() | 獲取緩衝區長度 | int|false | 內容大小判斷 |
ob_implicit_flush(bool $flag=true) | 開啟/關閉隱式刷新 | void | CLI 腳本實時輸出 |
PHP 支援多層 ob_start(),形成棧結構:
<?php
ob_start(); // Level 1: 外層緩衝 (頁面佈局)
echo "Header\n";
ob_start(); // Level 2: 內層緩衝 (內容區塊)
echo "Content\n";
$content = ob_get_clean(); // 獲取並關閉 Level 2
ob_start(); // Level 2 新:側邊欄
echo "Sidebar\n";
$sidebar = ob_get_clean();
$layout = "<html><body>$content<aside>$sidebar</aside></body></html>";
echo $layout;
ob_end_flush(); // 輸出 Level 1
<?php
// 必須在任何輸出前調用
ob_start();
// 業務邏輯可能產生輸出
echo "Processing...";
session_start(); // 可能輸出 Cookie header
setcookie('lang', 'zh-CN');
header('X-Powered-By: PHP');
// 最後統一輸出
ob_end_flush();
<?php
function render($template, $data = []) {
extract($data);
ob_start();
include $template;
return ob_get_clean();
}
// template.php
// <h1><?php echo $title; ?></h1>
// <p><?php echo $body; ?></p>
echo render('template.php', [
'title' => 'Hello',
'body' => 'World'
]);
<?php
$cacheFile = '/var/cache/page_' . md5($_SERVER['REQUEST_URI']) . '.html';
$cacheTime = 3600; // 1小時
if (file_exists($cacheFile) && time() - filemtime($cacheFile) < $cacheTime) {
readfile($cacheFile);
exit;
}
ob_start();
// 複雜頁面生成邏輯
include 'generate_page.php';
$html = ob_get_contents();
ob_end_flush();
file_put_contents($cacheFile, $html);
<?php
// 禁用所有緩衝
while (ob_get_level()) ob_end_clean();
ob_implicit_flush(true);
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // 禁用 Nginx 緩衝
for ($i = 1; $i <= 10; $i++) {
echo "data: Message $i at " . date('H:i:s') . "\n\n";
// ob_flush() 刷新 PHP 緩衝
// flush() 刷新到 Web 服務器/客戶端
ob_flush();
flush();
sleep(1);
}
<?php
// 自動檢測客戶端 Accept-Encoding 並壓縮
ob_start('ob_gzhandler');
// 或手動控制
if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', 'gzip') !== false) {
ob_start(function($buffer) {
return gzencode($buffer, 6);
});
header('Content-Encoding: gzip');
header('Vary: Accept-Encoding');
}
<?php
// 自動將相對路徑轉絕對路徑
ob_start(function($buffer) {
return preg_replace('/(src|href)=["\']\//', '$1="https://cdn.example.com/', $buffer);
});
echo '<img src="/logo.png">'; // 輸出: <img src="https://cdn.example.com/logo.png">
| 問題 | 原因 | 解決 |
|---|---|---|
| ob_start() 失效 | php.ini output_buffering = On 已預開啟 | 檢查 ob_get_level() > 0 判斷是否已在緩衝中 |
| 輸出亂碼/截斷 | 多層 ob_start 未正確配對關閉 | 使用 ob_get_level() 追蹤,異常時 while(ob_get_level()) ob_end_clean() |
| flush() 不生效 | Nginx/Apache 緩衝、瀏覽器緩衝、壓縮模組 | 關閉 gzip、設置 header('X-Accel-Buffering: no')、輸出足夠數據觸發瀏覽器渲染 |
| 內存耗盡 | 大文件/長腳本全量緩衝 | 分塊輸出、流式處理、關閉緩衝直接 echo |
| Session 鎖阻塞 | session_start() 開啟文件鎖,長腳本阻塞並發 | 早調用 session_write_close() 釋放鎖 |
<?php
// CLI 默認隱式刷新 (ob_implicit_flush=1)
// 每行 echo 直接輸出,無需 flush()
// 若需緩衝 (如捕獲輸出)
ob_start();
echo "Buffered\n";
$out = ob_get_clean(); // 獲取而非輸出
// 進度條實現
for ($i = 0; $i <= 100; $i++) {
echo "\rProgress: [$i%] " . str_repeat('=', $i/2);
usleep(50000);
}
echo "\nDone.\n";
ob_* 系列是 PHP 控制輸出流的核心工具。掌握開啟 → 捕獲/處理 → 輸出/丟棄的生命週期,理解巢狀棧機制,結合 flush() 實現實時輸出,能解決 90% 以上的輸出相關疑難雜症。現代框架 (Laravel/Symfony) 均在核心封裝了響應對象,底層即基於輸出緩衝實現。