輸出緩衝 (Output Buffering) 讓 PHP 腳本不直接向瀏覽器輸出,而是先存入記憶體緩衝區,支援內容修改、壓縮、延遲輸出、錯誤捕獲等進階用法。本文系統講解 ob_start、ob_get_clean、ob_flush、flush 等核心函數,並實戰示範動態輸出、內容過濾、佇列式輸出等場景。
| 函數 | 用途 | 回傳值 |
|---|---|---|
| ob_start(callable $callback?) | 開啟緩衝,可選回調處理輸出 | bool |
| ob_get_contents() | 取得緩衝區內容 (不清除) | string|false |
| ob_get_clean() | 取得內容並關閉緩衝 | string|false |
| ob_end_flush() | 輸出緩衝內容並關閉 | bool |
| ob_end_clean() | 丟棄緩衝內容並關閉 | bool |
| ob_flush() | 將 PHP 緩衝推送到 Web Server | void |
| flush() | 將 Web Server 緩衝推送到客戶端 | void |
| ob_get_level() | 巢狀緩衝層數 | int |
| ob_get_status() | 緩衝狀態陣列 | array |
<?php
ob_start(); // 開啟緩衝
echo "Hello World";
$out = ob_get_clean(); // 取得並關閉
$out = strtolower($out); // 修改內容
var_dump($out); // string(11) "hello world"
<?php
// 所有輸出自動轉大寫
ob_start(function($buffer) {
return strtoupper($buffer);
});
echo "hello";
echo "world";
// 輸出: HELLOWORLD
<?php
// 告訴瀏覽器這是分塊傳輸
header('Content-Type: text/plain; charset=utf-8');
header('X-Content-Type-Options: nosniff');
// 關閉預設緩衝 (php.ini output_buffering=Off 更好)
while (ob_get_level()) ob_end_clean();
$steps = ['初始化', '連接資料庫', '讀取資料', '處理邏輯', '生成報表', '完成'];
$spinner = ['-', '\\', '|', '/'];
foreach ($steps as $i => $step) {
// 模擬工作
usleep(500_000); // 0.5秒
// 輸出進度
$spin = $spinner[$i % 4];
echo "\r[$spin] $step... ";
// 關鍵:雙重 flush 確保送達瀏覽器
ob_flush();
flush();
}
echo "\n✅ 所有任務完成!\n";
<?php
// 模擬 multipart/x-mixed-replace (舊式串流)
header('Content-Type: multipart/x-mixed-replace; boundary=END_OF_PART');
echo "\n--END_OF_PART\n";
for ($i = 0; $i < 10; $i++) {
sleep(1);
echo "Content-Type: text/plain\n\n";
echo "Part $i at " . date('H:i:s') . "\n";
echo "--END_OF_PART\n";
ob_flush();
flush();
}
echo "Content-Type: text/plain\n\n";
echo "The End\n";
echo "--END_OF_PART--\n";
<?php
if (!function_exists('ob_clean')) {
function ob_clean(): bool {
if (@ob_end_clean()) {
return ob_start();
}
trigger_error('ob_clean() failed: no buffer to delete', E_USER_NOTICE);
return false;
}
}
<?php
function render(string $template, array $data = []): string {
extract($data);
ob_start();
include $template;
return ob_get_clean();
}
// 使用
$html = render('article.php', [
'title' => 'PHP 輸出緩衝',
'content' => '內容...',
]);
echo $html;
<?php
ob_start();
try {
// 可能產生輸出的危險操作
riskyOperation();
$output = ob_get_clean();
echo $output;
} catch (Throwable $e) {
ob_end_clean(); // 丟棄不完整輸出
echo "錯誤: " . $e->getMessage();
// 記錄日誌...
}
// PHP 內部緩衝 → Web Server (Apache/Nginx) 緩衝 → 網路 → 瀏覽器
ob_flush(); // PHP 緩衝 → Web Server
flush(); // Web Server 緩衝 → 網路/瀏覽器
// 實際使用必須連用
ob_flush();
flush();
php.ini output_buffering:若為 On/數值,PHP 會先緩衝,需 ob_end_flush() 或 ob_implicit_flush(true)zlib.output_compression:壓縮會延遲輸出,需關閉proxy_buffering off、Apache FlushMaxThreshold<?php
ob_start(); // Level 1
echo "A";
ob_start(); // Level 2
echo "B";
ob_start(); // Level 3
echo "C";
var_dump(ob_get_level()); // int(3)
$c = ob_get_clean(); // Level 3 關閉
$b = ob_get_clean(); // Level 2 關閉
$a = ob_get_clean(); // Level 1 關閉
echo "A=$a, B=$b, C=$c"; // A=A, B=B, C=C
| 現象 | 原因 | 解決 |
|---|---|---|
| flush 不生效 | output_buffering=On、zlib 壓縮、Nginx 緩衝 | php.ini 關閉、header 設定、Nginx proxy_buffering off |
| Headers already sent | ob_start 前有輸出、BOM、空白 | 腳本開頭無輸出、ob_start() 放最前 |
| 巢狀緩衝洩漏 | 未正確配對 ob_start/ob_end_* | 使用 try/finally、檢查 ob_get_level() |
| 記憶體耗盡 | 大量輸出未釋放緩衝 | 分塊輸出、ob_end_flush、串流模式 |
| 回調函數不執行 | ob_end_clean() 丟棄緩衝 | 用 ob_end_flush() 或 ob_get_clean() |
<?php
// PHP 8.1+ 纖程/非阻塞 或使用 ReactPHP/Swoole
// 簡易 Generator 串流
function streamResponse(): Generator {
yield "開始\n";
for ($i = 1; $i <= 5; $i++) {
yield "步驟 $i\n";
// 非阻塞等待...
}
yield "結束\n";
}
header('Content-Type: text/plain');
header('X-Accel-Buffering: no'); // Nginx 關閉緩衝
foreach (streamResponse() as $chunk) {
echo $chunk;
ob_flush();
flush();
}
輸出緩衝是 PHP 靈活控制 HTTP 回應的利器。核心掌握:ob_start 開啟、ob_get_clean 捕獲修改、ob_flush()+flush() 即時推送。現代開發中,模板引擎、框架回應物件、PSR-7 串流介面已封裝好底層細節,但理解原理對除錯 Headers already sent、優化大檔案下載、實現即時進度條、處理異常輸出等場景至關重要。