首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 分頁技術完整指南:原理、基礎實作與物件導向封裝

     編輯:PHP 相關知識

分頁核心原理

將資料庫結果集切割為固定大小的頁面顯示,需三個核心參數:

  • $PageSize:每頁筆數
  • $CurrentPageID:當前頁碼 (從 1 開始)
  • 總筆數 $totalRows (透過 SELECT COUNT(*) 取得)

衍生參數:

  • 總頁數 $numPages = ceil($totalRows / $PageSize)
  • 資料庫 Offset ($CurrentPageID - 1) * $PageSize

MySQL LIMIT 語法應用

-- 第 1 頁 (每頁 10 筆)
SELECT * FROM `table` ORDER BY id DESC LIMIT 0, 10;

-- 第 2 頁
SELECT * FROM `table` ORDER BY id DESC LIMIT 10, 10;

-- 通用模板
SELECT * FROM `table` ORDER BY id DESC LIMIT {(page-1)*size}, {size}

基礎程式碼實作 (Procedural)

<?php
// 1. 連線資料庫 (建議改用 PDO/MySQLi)
$link = mysqli_connect("localhost", "user", "pass", "dbname") or die("Connect failed: " . mysqli_connect_error());

// 2. 取得當前頁碼
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$pageSize = 10;

// 3. 查總筆數
$totalRows = 0;
$res = mysqli_query($link, "SELECT COUNT(*) AS cnt FROM `articles`");
if ($row = mysqli_fetch_assoc($res)) $totalRows = (int)$row['cnt'];

// 4. 計算總頁數
$numPages = $totalRows ? (int)ceil($totalRows / $pageSize) : 0;

// 5. 修正頁碼範圍
$page = min($page, max(1, $numPages));

// 6. 取得分頁資料
$offset = ($page - 1) * $pageSize;
$sql = "SELECT * FROM `articles` ORDER BY id DESC LIMIT $offset, $pageSize";
$result = mysqli_query($link, $sql);
$rows = mysqli_fetch_all($result, MYSQLI_ASSOC);

// 7. 產生分頁連結
$links = '';
if ($page > 1) {
    $links .= '<a href="?page=1">首頁</a> ';
    $links .= '<a href="?page=' . ($page - 1) . '">上一頁</a> ';
}
for ($i = max(1, $page - 2); $i <= min($numPages, $page + 2); $i++) {
    $links .= ($i == $page) ? "<strong>$i</strong> " : '<a href="?page=' . $i . '">' . $i . '</a> ';
}
if ($page < $numPages) {
    $links .= '<a href="?page=' . ($page + 1) . '">下一頁</a> ';
    $links .= '<a href="?page=' . $numPages . '">尾頁</a> ';
}

// 8. 輸出
foreach ($rows as $row) {
    echo "<div>" . htmlspecialchars($row['title']) . "</div>";
}
echo "<div class='pagination'>$links</div>";
?>

物件導向封裝:Pager 類別

<?php
// Pager.class.php
class Pager {
    public $pageSize = 10;
    public $currentPage = 1;
    public $totalRows = 0;
    public $sql = '';
    public $pdo; // PDO 實例

    // 衍生屬性
    public $numPages = 0;
    public $isFirstPage = false;
    public $isLastPage = false;
    public $prevPage = 0;
    public $nextPage = 0;

    public function __construct(PDO $pdo, array $options = []) {
        $this->pdo = $pdo;
        foreach ($options as $k => $v) {
            if (property_exists($this, $k)) $this->$k = $v;
        }
        $this->calculate();
    }

    private function calculate() {
        if ($this->totalRows === 0) {
            $stmt = $this->pdo->query("SELECT COUNT(*) FROM ($this->sql) AS t");
            $this->totalRows = (int)$stmt->fetchColumn();
        }
        $this->numPages = $this->totalRows ? (int)ceil($this->totalRows / $this->pageSize) : 0;
        $this->currentPage = max(1, min($this->currentPage, $this->numPages ?: 1));

        $this->isFirstPage = ($this->currentPage === 1);
        $this->isLastPage = ($this->currentPage === $this->numPages);
        $this->prevPage = $this->isFirstPage ? 0 : $this->currentPage - 1;
        $this->nextPage = $this->isLastPage ? 0 : $this->currentPage + 1;
    }

    // 取得資料 (回傳關聯陣列)
    public function getPageData() {
        if ($this->totalRows === 0) return [];
        $offset = ($this->currentPage - 1) * $this->pageSize;
        $stmt = $this->pdo->prepare("$this->sql LIMIT :offset, :limit");
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->bindValue(':limit', $this->pageSize, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    // 取得 PDOStatement (大資料集逐行處理,省記憶體)
    public function getDataLink() {
        if ($this->totalRows === 0) return false;
        $offset = ($this->currentPage - 1) * $this->pageSize;
        $stmt = $this->pdo->prepare("$this->sql LIMIT :offset, :limit");
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->bindValue(':limit', $this->pageSize, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt;
    }

    // 產生分頁 HTML (可依需求覆寫)
    public function renderLinks($baseUrl = '?') {
        if ($this->numPages <= 1) return '';
        $html = '<nav class="pagination">';
        if (!$this->isFirstPage) {
            $html .= '<a href="' . $baseUrl . 'page=1">首頁</a> ';
            $html .= '<a href="' . $baseUrl . 'page=' . $this->prevPage . '">上一頁</a> ';
        }
        $start = max(1, $this->currentPage - 3);
        $end = min($this->numPages, $this->currentPage + 3);
        for ($i = $start; $i <= $end; $i++) {
            $html .= ($i == $this->currentPage)
                ? '<span class="current">' . $i . '</span> '
                : '<a href="' . $baseUrl . 'page=' . $i . '">' . $i . '</a> ';
        }
        if (!$this->isLastPage) {
            $html .= '<a href="' . $baseUrl . 'page=' . $this->nextPage . '">下一頁</a> ';
            $html .= '<a href="' . $baseUrl . 'page=' . $this->numPages . '">尾頁</a> ';
        }
        $html .= '</nav>';
        return $html;
    }
}

使用範例

<?php
require 'Pager.class.php';
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');

$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$sql = "SELECT id, title, created_at FROM articles WHERE status = 1 ORDER BY id DESC";

$pager = new Pager($pdo, [
    'sql' => $sql,
    'pageSize' => 15,
    'currentPage' => $page
]);

$data = $pager->getPageData();
?>
<!DOCTYPE html>
<html>
<body>
    <div class="list">
        <?php foreach ($data as $row): ?>
            <article><h3><?= htmlspecialchars($row['title']) ?></h3></article>
        <?php endforeach; ?>
    </div>
    <?= $pager->renderLinks() ?>
</body>
</html>

資料庫相容性說明

不同資料庫分頁語法不同:

  • MySQL / PostgreSQL / SQLite:LIMIT offset, rows / LIMIT rows OFFSET offset
  • SQL Server (2012+):OFFSET offset ROWS FETCH NEXT rows ROWS ONLY
  • Oracle:ROWNUM 子查詢或 OFFSET ... FETCH (12c+)

使用 PDO 時,建議在 Pager 類別內依 $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) 動態產生對應 SQL,或採用 LIMIT 模擬函式庫。

進階優化建議

  • 游標分頁:大資料集避免 OFFSET 深分頁效能問題,改用 WHERE id > last_seen_id ORDER BY id LIMIT 10。
  • 快取總筆數:總筆數變動不頻繁時,用 Redis/Memcached 快取 COUNT(*) 結果。
  • 前端分頁元件化:分頁邏輯後端提供 JSON ({page, pageSize, total, data}),前端 Vue/React 元件渲染。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved