PHP 的 ob_start() 輸出緩衡控制函數是開發者工具箱中的「瑞士刀」。它不僅能優雅解決 headers already sent 經典錯誤,還能以極低成本實現頁面級快取,大幅降低資料庫壓力。本文從原理到實戰,手把手教你玩轉輸出緩衝。
HTTP 協議要求 Header 必須在 Body 之前發送。若在 header()、setcookie()、session_start() 之前有任何輸出(含空白字元、BOM、錯誤訊息),PHP 就會拋出錯誤:
Warning: Cannot modify header information - headers already sent by...
ob_start() 開啟輸出緩衝後,所有 echo、print、HTML 直輸出都會先存入記憶體緩衝區,而非直接發送給瀏覽器。此時呼叫 header() 完全安全,因為實際輸出被延後到腳本結束或手動 ob_end_flush() 時。
<?php ob_start(); // 開啟緩衝,必須在任何輸出之前 echo '頁面內容...
'; // 實際未輸出,存入緩衝區 // 此處可以安全操作 Header header('Content-Type: application/json'); setcookie('lang', 'zh-TW', time() + 3600); $content = ob_get_contents(); // 取得緩衝內容 ob_end_clean(); // 清空緩衝並關閉(不輸出) echo json_encode(['html' => $content]); // 統一輸出 ?>
場景:某頁面查詢複雜、資料更新頻率低(如分類導航、熱門文章),適合快取靜態 HTML 片段。
<?php
$cacheFile = __DIR__ . '/cache/category_nav.html';
$cacheTime = 300; // 5 分鐘快取
// 1. 快取有效直接輸出
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) {
readfile($cacheFile);
exit;
}
// 2. 開啟緩衝,生成動態內容
ob_start();
?>
<nav class="category-nav">
<ul>
<?php foreach (get_categories() as $cat): ?>
<li><a href="/category/<?= $cat['slug'] ?>"><?= $cat['name'] ?></a></li>
<?php endforeach; ?>
</ul>
</nav>
<?php
$html = ob_get_clean(); // 取得內容並關閉緩衝
// 3. 寫入快取檔(原子寫入避免並發損壞)
$tmpFile = $cacheFile . '.tmp';
file_put_contents($tmpFile, $html);
rename($tmpFile, $cacheFile); // 原子操作
echo $html; // 輸出給使用者
?>
ob_start() 支援巢狀呼叫,內層 ob_get_contents() 只取得當層緩衝內容:
<?php ob_start(); // 外層:全頁快取 echo 'Header'; ob_start(); // 內層:區塊快取 echo 'Dynamic Sidebar'; $sidebar = ob_get_clean(); // 只取得 Sidebar echo 'Main Content'; $page = ob_get_clean(); // 取得 Header + Sidebar + Main ?>
利用此特性可實現 ESI(Edge Side Includes)風格的片段快取:全頁快取 1 小時,側邊欄快取 5 分鐘,用戶資訊不快取。
| 坑點 | 後果 | 解決方案 |
|---|---|---|
| 忘記 ob_end_flush/clean | 記憶體洩漏、輸出重複 | 配合 try/finally 或 register_shutdown_function 確保清理 |
| 大檔案輸出未分塊 | 記憶體耗盡 | 大檔案用 readfile() 或 X-Sendfile,不要進緩衝區 |
| 快取鑰匙未區分用戶/語言 | 資料洩露、顯示錯誤 | 快取 Key 包含 user_id、locale、theme 等變因 |
| 緩衝區過大觸發 memory_limit | 腳本崩潰 | 分塊輸出、調大 memory_limit、或改用檔案流 |
手寫檔案快取適合中小專案快速落地。大型專案建議:
ob_start() 是 PHP 獨特且強大的機制,掌握它能解決 Header 衝突、實現零依賴快取、構建靈活的輸出管道。從「解決報錯」到「提升效能」,輸出緩衝是每位 PHP 開發者必修的內功。