每個頁面都有相同的 Header、Footer、導航欄,手工複製意味著修改一次需改動百個文件。PHP 的 include/require 機制原生支援服務端組件化。本文通過構建一個完整個人站點,演示公共配置、頁面佈局、計數器、反饋表單、站內搜索的模塊化實現。
project/
├── index.php # 首頁
├── contact.php # 聯系頁
├── resume.php # 簡歷頁
├── feedback.php # 反饋表單
├── search.php # 站內搜索
├── include/
│ ├── common.inc.php # 全站配置變量
│ ├── header.inc.php # 頁面頭部
│ ├── footer.inc.php # 頁面尾部
│ ├── counter.inc.php # 計數器函數
│ └── functions.inc.php # 通用函數
├── counter.txt # 計數器數據文件
└── data/ # 搜索索引目錄
include/common.inc.php<?php
// 站點元信息
define('SITE_NAME', '我的 PHP 個人站');
define('SITE_EMAIL', 'admin@example.com');
define('SITE_URL', 'https://example.com');
// 資料庫配置(如有)
define('DB_HOST', 'localhost');
define('DB_NAME', 'mysite');
define('DB_USER', 'user');
define('DB_PASS', 'pass');
// 公共變量
$siteTitle = SITE_NAME;
$siteEmailLink = "<a href=\"mailto:" . SITE_EMAIL . "\">" . SITE_EMAIL . "</a>";
include/header.inc.php<?php
// 接收 $pageTitle 變量,默認站點名
$title = $pageTitle ?? SITE_NAME;
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>= htmlspecialchars($title) ?> - = SITE_NAME ?>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<header>
<h1><a href="= SITE_URL ?>">= SITE_NAME ?></a></h1>
<nav>
<a href="/">首頁</a> |
<a href="/contact.php">聯系我</a> |
<a href="/resume.php">簡歷</a> |
<a href="/feedback.php">留言</a>
</nav>
</header>
<main>
include/footer.inc.php<?php // 無需接收變量,直接用常量 ?>
</main>
<footer>
<hr>
<p>© = date('Y') ?> <a href="mailto:= SITE_EMAIL ?>">= SITE_NAME ?></a>. All rights reserved.</p>
</footer>
</body></html>
index.php(含計數器)<?php
require_once __DIR__ . '/include/common.inc.php';
require_once __DIR__ . '/include/counter.inc.php';
$pageTitle = '首頁';
require_once __DIR__ . '/include/header.inc.php';
?>
<h2>歡迎來到我的站點</h2>
<p>這裡記錄我的學習筆記、項目作品、生活隨想。</p>
<div class="counter">
訪問統計:<strong>= getHitCount(__DIR__ . '/counter.txt') ?></strong> 次
</div>
<?php require_once __DIR__ . '/include/footer.inc.php'; ?>
include/counter.inc.php<?php
function getHitCount(string $file): int
{
$count = 0;
if (file_exists($file)) {
$count = (int)file_get_contents($file);
}
$count++;
// 原子寫入:臨時文件 + rename
$tmp = $file . '.tmp';
file_put_contents($tmp, (string)$count);
rename($tmp, $file);
return $count;
}
feedback.php<?php
require_once __DIR__ . '/include/common.inc.php';
$pageTitle = '留言反饋';
require_once __DIR__ . '/include/header.inc.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');
$errors = [];
if ($name === '') $errors[] = '請填寫姓名';
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = '郵箱格式不正確';
if ($message === '') $errors[] = '請填寫留言內容';
if (!$errors) {
$to = SITE_EMAIL;
$subject = '網站留言:' . $name;
$body = "姓名:$name\n郵箱:$email\n\n$message";
$headers = "From: $email\r\nReply-To: $email\r\nContent-Type: text/plain; charset=UTF-8";
if (mail($to, $subject, $body, $headers)) {
echo '<div class="success">留言已發送,謝謝!</div>';
} else {
echo '<div class="error">發送失敗,請稍後重試。</div>';
}
} else {
echo '<div class="error"><ul><li>' . implode('</li><li>', $errors) . '</li></ul></div>';
}
}
?>
<h2>給我留言</h2>
<form method="post">
<label>姓名:<input type="text" name="name" required></label><br>
<label>郵箱:<input type="email" name="email" required></label><br>
<label>留言:<br><textarea name="message" rows="5" required></textarea></label><br>
<button type="submit">提交</button>
</form>
<?php require_once __DIR__ . '/include/footer.inc.php'; ?>
search.php(grep 簡易版,生產請用 SQLite FTS/MeiliSearch)<?php
require_once __DIR__ . '/include/common.inc.php';
$pageTitle = '站內搜索';
require_once __DIR__ . '/include/header.inc.php';
$query = trim($_GET['q'] ?? '');
$results = [];
if ($query !== '') {
// 安全:逃逸 shell 參數
$escaped = escapeshellarg($query);
$cmd = "grep -r -i -l $escaped " . escapeshellarg(__DIR__ . '/data') . " 2>/dev/null";
$files = [];
exec($cmd, $files);
foreach ($files as $file) {
$rel = str_replace(__DIR__ . '/data/', '', $file);
$content = file_get_contents($file);
// 取首個匹配行作摘要
if (preg_match('/.*' . preg_quote($query, '/') . '.*/i', $content, $m)) {
$snippet = htmlspecialchars(mb_substr($m[0], 0, 160));
$results[] = ['file' => $rel, 'snippet' => $snippet];
}
}
}
?>
<h2>站內搜索</h2>
<form method="get">
<input type="text" name="q" value="= htmlspecialchars($query) ?>" placeholder="輸入關鍵詞">
<button type="submit">搜索</button>
</form>
<?php if ($query !== ''): ?>
<?php if ($results): ?>
<ul>
<?php foreach ($results as $r): ?>
<li><a href="/data/= $r['file'] ?>">= $r['file'] ?></a>: = $r['snippet'] ?>...</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>未找到包含「= htmlspecialchars($query) ?>」的內容。</p>
<?php endif; ?>
<?php endif; ?>
<?php require_once __DIR__ . '/include/footer.inc.php'; ?>
| 特性 | include | require |
|---|---|---|
| 文件不存在 | E_WARNING,腳本繼續 | E_COMPILE_ERROR,腳本停止 |
| 條件引入 | ✅ 運行時執行 | ❌ 編譯時必須存在 |
| 返回值 | 可獲取被引文件 return 值 | 同 |
| 推薦用法 | 模板片段、可選組件 | 核心配置、函數庫、類定義 |
現代最佳實踐:統一用 require_once(或 Composer 自動加載類文件),避免重複引入、路徑錯誤靜默失敗。
這個小站雖簡單,卻完整展示了 PHP 「配置分離 → 佈局組件 → 功能模塊 → 數據持久化 → 表單處理 → 搜索索引」 的工程化思維。掌握 include/require 組件化,你就能在任何 PHP 專案中快速構建可維護的頁面結構,告別「改個版權年份改 100 個文件」的噩夢。