PHP 的輸出緩衝區(Output Buffering)機制允許我們攔截腳本原本應直接送往瀏覽器的輸出,將其暫存在記憶體中,再由程式決定何時、以何種方式釋出。這是實現靜態化、頁面快取、內容壓縮等功能的基石。
| 函式 | 用途 | 回傳值 |
|---|---|---|
ob_start(callable $callback = null, int $chunk_size = 0, int $flags = PHP_OUTPUT_HANDLER_STDFLAGS) | 開啟輸出緩衝區,可選擇性指定回呼函式處理輸出內容 | bool |
ob_get_contents() | 取得目前緩衝區內容 | string|false |
ob_get_clean() | 取得內容並關閉/清空緩衝區(等同 ob_get_contents() + ob_end_clean()) | string |
ob_end_flush() | 輸出緩衝區內容並關閉緩衝區 | bool |
ob_end_clean() | 丟棄緩衝區內容並關閉緩衝區 | bool |
ob_flush() | 刷新 PHP 輸出緩衝區到 Web Server(Apache/Nginx) | bool |
flush() | 刷新 Web Server 緩衝區到瀏覽器(需搭配 ob_flush() 使用) | void |
ob_get_length() | 取得緩衝區內容長度(位元組) | int|false |
ob_implicit_flush(bool $enable = true) | 開啟/關閉絕對刷新模式(每次輸出自動 flush) | void |
<?php
// 1. 在腳本最開頭開啟緩衝
ob_start();
// 2. 正常的業務邏輯與輸出
echo "<h1>歡迎來到靜態頁面</h1>";
echo "<p>生成時間:" . date('Y-m-d H:i:s') . "</p>";
// ... 這裡可以包含複雜的模板渲染、資料庫查詢等
// 3. 在腳本結尾取得內容並寫入檔案
$html = ob_get_clean(); // 同時取得內容並關閉緩衝
$file = __DIR__ . '/cache/page_' . md5($_SERVER['REQUEST_URI']) . '.html';
file_put_contents($file, $html);
// 4. 輸出給瀏覽器(或重導向至靜態檔)
echo $html;
?>
避免每次請求都重新生成,依據檔案修改時間判斷是否過期。
<?php
$cacheDir = __DIR__ . '/cache/';
$cacheFile = $cacheDir . 'page_' . md5($_SERVER['REQUEST_URI']) . '.html';
$ttl = 1800; // 30 分鐘(秒)
// 若快取存在且未過期,直接輸出並結束
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $ttl)) {
readfile($cacheFile);
exit;
}
// 否則重新生成
ob_start();
// ... 業務邏輯與輸出 ...
$html = ob_get_clean();
// 確保快取目錄存在
if (!is_dir($cacheDir)) mkdir($cacheDir, 0755, true);
file_put_contents($cacheFile, $html, LOCK_EX);
echo $html;
?>
<?php
class StaticPageCache {
private string $cacheDir;
private int $ttl;
private string $fileExtension = '.html';
public function __construct(string $cacheDir, int $ttl = 3600) {
$this->cacheDir = rtrim($cacheDir, '/') . '/';
$this->ttl = $ttl;
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
/** 生成快取鍵值(可依需求覆寫) */
protected function getCacheKey(): string {
return 'page_' . md5($_SERVER['REQUEST_URI'] . '?' . $_SERVER['QUERY_STRING']);
}
/** 檢查快取是否有效 */
public function isValid(): bool {
$file = $this->cacheDir . $this->getCacheKey() . $this->fileExtension;
return file_exists($file) && (time() - filemtime($file) < $this->ttl);
}
/** 直接輸出快取內容並終止腳本 */
public function serve(): void {
$file = $this->cacheDir . $this->getCacheKey() . $this->fileExtension;
header('Content-Type: text/html; charset=utf-8');
readfile($file);
exit;
}
/** 開始緩衝(在業務邏輯前呼叫) */
public function start(): void {
ob_start();
}
/** 結束緩衝並寫入快取(在業務邏輯後呼叫) */
public function save(): string {
$html = ob_get_clean();
$file = $this->cacheDir . $this->getCacheKey() . $this->fileExtension;
file_put_contents($file, $html, LOCK_EX);
echo $html;
return $html;
}
/** 一鍵式快取流程 */
public function run(callable $render): void {
if ($this->isValid()) {
$this->serve();
}
$this->start();
$render();
$this->save();
}
}
// 使用範例
$cache = new StaticPageCache(__DIR__ . '/cache', 1800);
$cache->run(function() {
// 這裡放置完整的頁面渲染邏輯
include 'templates/header.php';
include 'templates/content.php';
include 'templates/footer.php';
});
?>
ob_start() 可多次呼叫,形成堆疊結構。使用 ob_get_level() 確認層級,避免誤關外層緩衝。ob_start() 前已有輸出(含 BOM、空白字元),緩衝將失效。確保 PHP 標籤前無任何字元。ob_start('ob_gzhandler') 可自動對支援 gzip 的瀏覽器壓縮輸出。session_start() 會送出 Header,需在 ob_start() 之後呼叫,或使用 session_write_close() 提早釋放鎖。| 指標 | 動態 PHP 渲染 | 靜態 HTML 直讀 | 差異 |
|---|---|---|---|
| CPU 使用率 | 高(解析執行 PHP、資料庫查詢、模板渲染) | 極低(僅靜態檔 I/O) | 降低 90%+ |
| 記憶體佔用 | 高(PHP 運行時、框架開銷) | 極低(Web Server 靜態檔處理) | 降低 80%+ |
| 回應時間 | 50-500ms | 1-10ms | 提升 10-50 倍 |
| 併發承載 | 受限於 PHP-FPM 進程數 | 受限於 Web Server 連接數/頻寬 | 提升數量級 |
PHP 輸出緩衡機制配合檔案快取,是零依賴實現全站靜態化的經典方案。透過封裝可複用的快取類別、制定合理的 TTL 與失效策略、處理動態區塊與壓縮輸出等細節,即可在不引入 Redis、Varnish 等外部基礎設施的前提下,大幅提升網站效能與抗壓能力。對於內容導向、讀多寫少的站點(部落格、新聞、電商商品頁),這是性價比最高的優化手段。