動態頁面每次請求都需執行 PHP、查詢數據庫、渲染模板,高並發下資源消耗大。靜態化將動態生成的 HTML 直接寫入文件,後續請求由 Web 服務器 (Nginx/Apache) 直接發送文件,性能提升 10-100 倍,同時減輕數據庫壓力,利於 SEO。
PHP 的 ob_start() 系列函數可捕獲腳本輸出 (echo、HTML 代碼块),不直接發送給瀏覽器,而是存入內存緩衝區,最後由 ob_get_contents() 獲取內容寫入文件。
<?php
/**
* 靜態頁面生成器
* 支持按日期自動分目錄、自定義文件命名、輸出緩衝捕獲
*/
class CreateHtml {
private $rootDir = 'article'; // 靜態文件根目錄
private $dirFormat = 'Y/m/d'; // 子目錄格式
private $filePrefix = ''; // 文件名前綴
private $fileExt = '.html'; // 副檔名
public function __construct($rootDir = 'article', $dirFormat = 'Y/m/d') {
$this->rootDir = rtrim($rootDir, '/\\');
$this->dirFormat = $dirFormat;
}
/** 設置文件名前綴 */
public function setPrefix($prefix) {
$this->filePrefix = $prefix;
return $this;
}
/** 設置副檔名 */
public function setExtension($ext) {
$this->fileExt = $ext;
return $this;
}
/** 開始捕獲輸出 */
public function start() {
ob_start();
return $this;
}
/** 結束捕獲並寫入文件 */
public function end($fileId = '') {
$html = ob_get_contents();
ob_end_clean(); // 獲取後清空緩衝,不輸出到瀏覽器
// 構建存儲路徑
$dateDir = date($this->dirFormat);
$saveDir = $this->rootDir . DIRECTORY_SEPARATOR . $dateDir;
// 遞歸創建目錄
if (!is_dir($saveDir)) {
if (!mkdir($saveDir, 0755, true)) {
throw new RuntimeException("無法創建目錄: $saveDir");
}
}
// 生成文件名:前綴 + 時間戳 + 隨機數 + ID + 副檔名
$rand = mt_rand(1000, 9999);
$fileName = sprintf('%s%s_%s_%s%s',
$this->filePrefix,
date('YmdHis'),
$rand,
$fileId ?: 'index',
$this->fileExt
);
$filePath = $saveDir . DIRECTORY_SEPARATOR . $fileName;
// 寫入文件 (獨佔鎖防止併發寫入衝突)
$fp = fopen($filePath, 'w');
if ($fp === false) {
throw new RuntimeException("無法打開文件寫入: $filePath");
}
if (flock($fp, LOCK_EX)) {
fwrite($fp, $html);
flock($fp, LOCK_UN);
}
fclose($fp);
// 返回生成的相對路徑,供前端跳轉或記錄
return $dateDir . '/' . $fileName;
}
/** 直接生成:傳入 HTML 字符串,無需輸出緩衝 */
public function generate($html, $fileId = '') {
// 同樣的目錄/文件名邏輯...
// 省略,調用 end() 邏輯即可
}
}
?>
推薦將模板與邏輯分離,控制器調用模板生成靜態文件。
<?php
// controller.php
require 'CreateHtml.php';
require 'db.php'; // 數據庫連接
$articleId = (int)($_GET['id'] ?? 0);
$article = $db->query("SELECT * FROM articles WHERE id = ?", $articleId);
if (!$article) {
die('文章不存在');
}
// 實例化生成器
$generator = new CreateHtml('static_articles', 'Y/m');
// 可選:設置前綴
$generator->setPrefix('art_');
// 開始緩衝
$generator->start();
// 引入模板文件 (模板中可直接使用 $article 變量)
include 'templates/article_detail.php';
// 結束並保存,獲取靜態文件路徑
$staticPath = $generator->end($articleId);
echo "靜態頁已生成: /$staticPath
";
echo '<a href="/' . $staticPath . '" target="_blank">查看靜態頁</a>';
?>
<!-- templates/article_detail.php -->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($article['title']) ?></title>
<meta name="description" content="<?= htmlspecialchars($article['summary']) ?>">
</head>
<body>
<article>
<h1><?= htmlspecialchars($article['title']) ?></h1>
<div class="meta">
作者: <?= htmlspecialchars($article['author']) ?> |
時間: <?= date('Y-m-d H:i', $article['created_at']) ?>
</div>
<div class="content">
<?= $article['content'] ?> <!-- 假設內容已是安全 HTML -->
</div>
</article>
</body>
</html>
<?php
// 更新文章時刪除舊靜態文件
function updateArticle($id, $data) {
global $db;
$db->update('articles', $data, ['id' => $id]);
// 查找並刪除舊靜態文件 (需記錄路徑或按規則查找)
$oldPath = findStaticPath($id); // 實現查找邏輯
if ($oldPath && file_exists($oldPath)) {
unlink($oldPath);
}
// 異步重新生成 (推入隊列)
Queue::push('generate_static', ['id' => $id]);
}
?>
完全靜態化難以處理「登錄用戶顯示不同內容」。解決方案:
CreateHtml 類演示了輸出緩衝靜態化的核心機制。生產環境建議結合 模板引擎 (Blade/Twig/Smarty)、消息隊列異步生成、Nginx try_files 直輸、緩存失效策略 構建完整靜態化系統。對於高並發讀多寫少場景 (文章、商品詳情、列表頁),靜態化是性能優化的「銀彈」。