PHP 生成靜態 HTML 是經典的「以空間換時間」策略:將動態渲染結果持久化為靜態檔案,Web 伺服器直接回傳,完全繞過 PHP 解析,極大提升併發承載。本文從資料庫設計、模板規範、生成流程、更新刪除策略四個維度,完整呈現一套可落地的靜態化方案。
CREATE TABLE `news` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(255) NOT NULL,
`content` TEXT NOT NULL,
`path` VARCHAR(255) NOT NULL COMMENT '靜態檔案相對路徑',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `uk_path` (`path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
關鍵點:path 唯一索引確保路徑不衝突;updated_at 支援增量重新生成。
採用簡單佔位符語法:{欄位名},如 {title}、{content}。
{# model.htm #}
{title}
{title}
{content}
優點:無需模板引擎依賴,str_replace 即可處理,效能極高。
add.php<?php
require_once 'conn.php'; // PDO 連線
$title = $_POST['title'] ?? '';
$content = $_POST['content'] ?? '';
if (!$title || !$content) {
die('標題與內容不能為空');
}
// 1. 生成唯一靜態路徑:採用自增 ID + .html
$stmt = $pdo->prepare("SELECT COALESCE(MAX(id), 0) + 1 AS next_id FROM news");
$stmt->execute();
$nextId = $stmt->fetchColumn();
$path = "news/{$nextId}.html"; // 相對路徑
// 2. 寫入資料庫(先入庫再生成,保證一致性)
$stmt = $pdo->prepare("INSERT INTO news (title, content, path) VALUES (?, ?, ?)");
$stmt->execute([$title, $content, $path]);
// 3. 讀取模板並替換
$template = file_get_contents('model.htm');
$replacements = [
'{title}' => $title,
'{content}' => $content,
'{created_at}' => date('Y-m-d H:i:s'),
];
$html = strtr($template, $replacements); // strtr 比多次 str_replace 更快
// 4. 確保目錄存在並寫入檔案
$fullPath = __DIR__ . '/' . $path;
$dir = dirname($fullPath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($fullPath, $html);
// 5. 回應
echo "查看生成的靜態頁";
| 操作 | 資料庫 | 靜態檔案 | 一致性保障 |
|---|---|---|---|
| 更新 | UPDATE news SET title=?, content=?, updated_at=NOW() WHERE id=? | 重新生成同路徑檔案覆蓋 | 事務包裹 DB + 檔案寫入(或補償機制) |
| 刪除 | DELETE FROM news WHERE id=? | unlink($fullPath) | 先刪檔案再刪 DB,失敗則保留檔案由定時任務清理 |
| 批量重建 | 遍歷所有記錄 | 全量重新生成 | 低峰期執行,配合 CDN 清除 |
使用 INSERT 後的 lastInsertId 作為路徑依據,避免預查 MAX(id) 的競態條件:
$stmt = $pdo->prepare("INSERT INTO news (title, content, path) VALUES (?, ?, '')");
$stmt->execute([$title, $content]);
$id = $pdo->lastInsertId();
$path = "news/{$id}.html";
$pdo->prepare("UPDATE news SET path=? WHERE id=?")->execute([$path, $id]);
$tmpFile = $fullPath . '.tmp.' . uniqid();
file_put_contents($tmpFile, $html);
rename($tmpFile, $fullPath); // 原子替換,避免讀取到半寫入內容
// 簡單快取:生產環境建議用 APCu/Redis
static $templateCache = [];
if (!isset($templateCache['model.htm'])) {
$templateCache['model.htm'] = file_get_contents('model.htm');
}
$html = strtr($templateCache['model.htm'], $replacements);
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| PHP 生成靜態檔 | 無額外依賴、完全可控、CDN 友善 | 部署複雜、多機同步難、更新延遲 | 內容型站點、流量大、更新頻率低 |
| Nginx fastcgi_cache | 零代碼改動、支援快取清除 API、多機共享 | 需配置快取鍵、無法處理極個性化內容 | 大多數動態頁面、微服務架構 |
| Varnish / Cloudflare | 邊緣快取、全球加速、強大清除機制 | 運維成本高、調試困難 | 高流量、全球用戶、靜態資源為主 |
| SSG (Astro/Next.js/Nuxt) | 構建時生成、極致效能、現代開發體驗 | 技術棧遷移成本大、動態能力受限 | 新專案、內容導向、團隊前端能力強 |
技術無優劣,唯場景適配。理解原理,理性選型,才是架構師的基本功。