分頁是 Web 開發最基礎也最常見的功能。一個好的分頁類別應具備:自動保留原有查詢參數、支援首頁/尾頁/上下頁/數字頁碼/下拉跳頁、全域變數隔離不污染命名空間、可自訂 HTML 結構與 CSS 樣式。本文提供一個生產級的 PageDivide 函式完整實作與使用教學。
<?php
/**
* 通用分頁函式
* @param int $total 總筆數
* @param int $shownu 每頁筆數,預設 20
* @param string $url 目標 URL,留空自動偵測 $_SERVER['REQUEST_URI']
* @return void 透過全域變數 $page, $sqlfirst, $pagecon 輸出結果
*/
function pageDivide(int $total, int $shownu = 20, string $url = ''): void {
// 靜態快取每頁筆數,供其他地方(如 SQL LIMIT)使用
$GLOBALS['shownu'] = $shownu;
// 1. 目前頁碼
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
// 2. 目標 URL 處理:保留原有查詢參數,移除舊 page 參數
if (!$url) {
$url = $_SERVER['REQUEST_URI'] ?? '';
}
$parsed = parse_url($url);
$query = $parsed['query'] ?? '';
if ($query) {
// 移除舊的 page=xxx
$query = preg_replace('/(&?)page=\d+/', '', $query);
$query = trim($query, '&');
$url = str_replace($parsed['query'], $query, $url);
$separator = $query ? '&' : '?';
} else {
$separator = '?';
}
$url .= $separator . 'page=';
// 3. 分頁計算
$lastpg = (int)ceil($total / $shownu); // 總頁數
$page = min($page, $lastpg); // 修正超出範圍
$prepg = $page - 1; // 上一頁
$nextpg = ($page == $lastpg) ? 0 : $page + 1; // 下一頁(0 表示無)
$sqlfirst = ($page - 1) * $shownu; // SQL LIMIT 起始位移
// 4. 組裝分頁導航 HTML
$pagecon = "顯示第 " . ($total ? $sqlfirst + 1 : 0) . "-" . min($sqlfirst + $shownu, $total) . " 筆,共 $total 筆";
if ($lastpg <= 1) {
$GLOBALS['page'] = $page;
$GLOBALS['sqlfirst'] = $sqlfirst;
$GLOBALS['pagecon'] = $pagecon;
return; // 單頁直接返回
}
// 首頁 / 上一頁
$pagecon .= $page != 1 ? " 首頁 " : " 首頁 ";
$pagecon .= $prepg ? " 上一頁 " : " 上一頁 ";
// 數字頁碼(可選:只顯示當前頁前後各 2 頁)
$window = 2;
$start = max(1, $page - $window);
$end = min($lastpg, $page + $window);
for ($i = $start; $i <= $end; $i++) {
if ($i == $page) {
$pagecon .= " $i ";
} else {
$pagecon .= " $i ";
}
}
// 下一頁 / 尾頁
$pagecon .= $nextpg ? " 下一頁 " : " 下一頁 ";
$pagecon .= $page != $lastpg ? " 尾頁 " : " 尾頁 ";
// 下拉跳頁選單
$pagecon .= " 跳至 頁,共 $lastpg 頁";
// 5. 寫入全域變數供呼叫端使用
$GLOBALS['page'] = $page;
$GLOBALS['sqlfirst'] = $sqlfirst;
$GLOBALS['pagecon'] = $pagecon;
}
<?php
require_once 'pageDivide.php';
// 1. 取得總筆數(建議用 COUNT(*) 而非 fetch_all 再 count)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->query("SELECT COUNT(*) FROM articles WHERE status = 1");
$total = (int)$stmt->fetchColumn();
// 2. 呼叫分頁函式
pageDivide($total, 10); // 每頁 10 筆
// 3. 取得分頁參數執行查詢
$page = $GLOBALS['page'];
$offset = $GLOBALS['sqlfirst'];
$limit = $GLOBALS['shownu'];
$stmt = $pdo->prepare("SELECT * FROM articles WHERE status = 1 ORDER BY id DESC LIMIT ?, ?");
$stmt->bindValue(1, $offset, PDO::PARAM_INT);
$stmt->bindValue(2, $limit, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 4. 輸出資料
foreach ($rows as $row) {
echo "{$row['title']}";
}
// 5. 輸出分頁導航
echo $GLOBALS['pagecon'];
若專案使用 Bootstrap 5,可修改組裝 HTML 區段:
// 替換原本的字串拼接為:
$pagecon = "";
| 問題 | 原因 | 解決 |
|---|---|---|
| 分頁連結遺失原有查詢參數(如 ?keyword=php) | URL 重組時未正確保留 query string | 使用 parse_url + preg_replace 精確移除舊 page 參數,其餘保留 |
| 最後一頁資料筆數不足導致 offset 越界 | 未對 $page 做 min($lastpg) 修正 | 函式內已內建 $page = min($page, $lastpg) |
| 多語系專案需調整文字 | 字串硬編碼在函式內 | 將文字提取為陣列參數或語系檔,函式接受 $lang 參數 |
| API 回傳 JSON 需要分頁中繼資料而非 HTML | 函式直接輸出 HTML | 重構為回傳陣列 ['current'=>$page, 'total'=>$lastpg, 'per_page'=>$shownu, 'links'=>[...]] |
pageDivide 以單一函式封裝「計算、URL 處理、HTML 產生」三大職責,適合中小型專案快速整合。大型專案建議採用 分頁器物件(Paginator) 模式(如 Laravel LengthAwarePaginator),支援多種呈現層(HTML、JSON、API Resource)並易於單元測試。