PHP 基本分頁技巧

作為一個網頁開發人員,你經常要用容易閱讀的格式把數據顯示給用戶,舉例說你要從數據庫讀取一份雇員名單,並在網頁上羅列出來,若果名單只有區區十 多人,用一個簡單迴圈把所有人列印出來便好了,不是很簡單嗎?但若果你有五十名雇員又如何呢?一百人?一千人?把這麼多人一口氣羅列出來顯然不是一個好主意,Crayon Violent 在 PHP Freaks 寫了一篇教學文件,介紹如何透過 PHP 來實作分頁。

同一時間從數據庫扯出所有數據,你的用戶可能會一面呆等一面納悶,系統究竟發什麼神經老是沒有輸出?千呼萬喚有結果了,得到的竟然是一本完完整整的「三國演義」,在一頁版面上列印出來!據說古人的確是把不論長短的文章寫在一匹絹布上,一種好像稱為卷軸的東西,詳情我也不清楚,但廿一世紀的你不會也這麼做吧?

聰明的做法是把一份長長的名單分割成很多小段,每次只從數據庫提取及顯示一段,這可以大幅降低伺服器的工作量,也提升網頁的下載速度,用戶也比較容易消化屏幕上的資料,不會一下子被你弄至消化不良,這種做法就是分頁。

一個基本的分頁函式初看起來可能又長又可怖,但其實你只要閉上眼睛,深吸一口氣,然後閱讀每一個分段,便會發現它其實是很簡單的東西。根據 Crayon 多年來在論壇上幫助別人的經驗,人們對分頁的最大困難在於搞不清這種技術叫作什麼!既然我們搞定了最困難的部份,餘下的便易如反掌了,不是嗎?

首先,我們要在數據庫中建立一個資料表,並放進一些數據,這裏不會討論安裝數據庫、建立資料表等細節,若果你不清楚這些東西,這份教學文件並不適合你。資料表的內容並不重要,若果你已經有一個滿載數據的資料表可作為測試用途,歡迎把以下程式中資料表和欄位的名稱更改,為了方便說明,文中的資料表名稱 是「numbers」,它有兩個欄位:「number」和「id」,兩者的類型都是 int,其中「id」是自動遞增的。

若果你打算用這些名稱自行建立資料表,不妨用以下的程式片段來產生測試用的數據:

$conn = mysql_connect('localhost','dbusername','dbpassword') or trigger_error("SQL", E_USER_ERROR);
$db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR);

for ($x = 0; $x < 106; $x++) {
$number = rand(100,999);
$sql = “INSERT INTO numbers (number, id) VALUES ($number, ”)”;
$query = mysql_query($sql, $conn) or trigger_error(“SQL”, E_USER_ERROR);
}
?>

長話短說:這個程式會產生 106 個隨機的 3 位數,為什麼是 106 個呢?這只是隨便選的,而且,分頁程式每次會顯示 10 筆資料,這樣便可以保證最後一頁少於 10 筆資料。為什麼是 3 位數呢?為什麼最後一頁要顯示 6 筆資料?這些全部都是隨意選擇的,沒有特別的原因,請相信我。

好了,正式開始前我們先看看偉大的分頁程式完整版本,很多教學文件把程式解剖成一小段一小段,讀者們被逼重複多次的複製和貼上,十分令人困擾,但偏偏與本文的宗旨互相矛盾,不是很諷刺嗎?

// 數據庫連結資料
$conn = mysql_connect('localhost','dbusername','dbpass') or trigger_error("SQL", E_USER_ERROR);
$db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR);

// 計算資料表中有多少列
$sql = “SELECT COUNT(*) FROM numbers”;
$result = mysql_query($sql, $conn) or trigger_error(“SQL”, E_USER_ERROR);
$r = mysql_fetch_row($result);
$numrows = $r[0];

// 每頁顯示的列數
$rowsperpage = 10;
// 計算總共需要多少頁
$totalpages = ceil($numrows / $rowsperpage);

// 取得當前的頁數,或者顯示預設的頁數
if (isset($_GET[‘currentpage’]) && is_numeric($_GET[‘currentpage’])) {
// 把變量的類型轉換成 int
$currentpage = (int) $_GET[‘currentpage’];
} else {
// 預設的頁數
$currentpage = 1;
} // end if

// 若過當前的頁數大於頁數總數
if ($currentpage > $totalpages) {
// 把當前頁數設定為最後一頁
$currentpage = $totalpages;
} // end if
// 若果當前的頁數小於 1
if ($currentpage < 1) {
// 把當前頁數設定為 1
$currentpage = 1;
} // end if

// 根據當前頁數計算名單的起始位置
$offset = ($currentpage – 1) * $rowsperpage;

// 從數據庫取資料
$sql = “SELECT id, number FROM numbers LIMIT $offset, $rowsperpage”;
$result = mysql_query($sql, $conn) or trigger_error(“SQL”, E_USER_ERROR);

// 若果還有資料的話…
while ($list = mysql_fetch_assoc($result)) {
// 列印資料
echo $list[‘id’] . ” : ” . $list[‘number’] . ”
“;
} // end while

/****** 建立分頁連結 ******/
// 顯示的頁數範圍
$range = 3;

// 若果正在顯示第一頁,無需顯示「前一頁」連結
if ($currentpage > 1) {
// 使用 << 連結回到第一頁
echo ” << “;
// 前一頁的頁數
$prevpage = $currentpage – 1;
// 使用 < 連結回到前一頁
echo ” < “;
} // end if

// 顯示當前分頁鄰近的分頁頁數
for ($x = (($currentpage – $range) – 1); $x < (($currentpage + $range) + 1); $x++) {
// 如果這是一個正確的頁數…
if (($x > 0) && ($x <= $totalpages)) {
// 如果這一頁等於當前頁數…
if ($x == $currentpage) {
// 不使用連結, 但用高亮度顯示
echo ” [$x] “;
// 如果這一頁不是當前頁數…
} else {
// 顯示連結
echo ” $x “;
} // end else
} // end if
} // end for

// 如果不是最後一頁, 顯示跳往下一頁及最後一頁的連結
if ($currentpage != $totalpages) {
// 下一頁的頁數
$nextpage = $currentpage + 1;
// 顯示跳往下一頁的連結
echo ” > “;
// 顯示跳往最後一頁的連結
echo ” >> “;
} // end if
/****** 完成建立分頁連結 ******/
?>

好了,就是這麼多代碼,它的基本構思是:

  • 計算你的資料表有多少列
  • 根據每頁你想顯示的列數,計算有多少分頁
  • 照出當前的頁數
  • 從數據庫提取需要顯示的資料
  • 顯示資料
  • 製作一些「第一頁」、「前一頁」、當前頁鄰近的分頁、「下一頁」和「最後一頁」的連結

上面的原碼有很多注釋,所以你應該可以弄清楚是怎麼回事,無論如何,下面我們會把它拆解分析,好,繼續前進……

// 數據庫連結資料
$conn = mysql_connect('localhost','dbusername','dbpass') or trigger_error("SQL", E_USER_ERROR);
$db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR);

以上是幾行很顯然只是用來連接數據庫。

// 計算資料表中有多少列
$sql = "SELECT COUNT(*) FROM numbers";
$result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR);
$r = mysql_fetch_row($result);
$numrows = $r[0];

知道了資料表有多少列,我們才知道有多少分頁,因此,我們執行基本的 count(*) 查詢並把接過儲存在 $numrows。

// 每頁顯示的列數
$rowsperpage = 10;
// 計算總共需要多少頁
$totalpages = ceil($numrows / $rowsperpage);

$rowsperpage 是每頁顯示的列數,我們把總列數($numrows)除以每頁的列數($rowsperpage),便知道有多少分頁需要顯示,由於不會有半頁(除非給你的愛犬啃掉了),我們使用 celi() 來計算確實的頁數。

// 取得當前的頁數,或者顯示預設的頁數
if (isset($_GET['currentpage']) && is_numeric($_GET['currentpage'])) {
// 把變量的類型轉換成 int
$currentpage = (int) $_GET['currentpage'];
} else {
// 預設的頁數
$currentpage = 1;
} // end if

當你點擊一個分頁的連結時,目標分頁的頁數會透過 GET 方法傳到伺服器,這裏正是要取得這個分頁頁數。由於第一次進入這個程式時並不會傳送這個變量,所以我們必須檢查流覽器是否傳來這個變量,與及它是否一個整數,說不定有人在 URL 手動更改這個變量的數值。

如果接收到這個變量,又是一個數字的話,我們便把它強制轉換為 int(整數)類型,原因是倘若有人在 URL 中把這個變量設定為 9.75,它便會轉換成第 9 分頁,除非你是哈利波特,否則應該很清楚世界上沒有第 9.75 分頁。

如果輸入的不是數字,我們預設為第一頁。

// 若過當前的頁數大於總頁數...
if ($currentpage > $totalpages) {
// 把當前頁數設定為最後一頁
$currentpage = $totalpages;
} // end if
// 若果當前的頁數小於 1...
if ($currentpage < 1) {
// 把當前頁數設定為 1
$currentpage = 1;
} // end if

下 一步是檢查這是否一個有效的頁碼,原因是倘若我們的小說只有 400 頁,有一個不滿小說結局,或者不肯相信小說就此結束的人,幻想我們還有一個隱藏起來的第 401 頁,於是直接在 URL 中把頁數更改為 401,那時怎麼辦?所以啊……如果有人輸入一個負數的頁數,或者超過總頁數的數字,我們分別把它預設為 1 和總頁數。

// 根據當前頁數計算名單的起始位置
$offset = ($currentpage - 1) * $rowsperpage;

我 們總共有 106 筆資料,但每次只顯示 10 筆,所以要計算從哪一筆資料開始提取,例如第 8 頁的起始列是第 80 筆資料,不過事實上應該是第 70 筆,因為電腦總愛從零開始計算,因此我們把目前的頁數(8)減去一,乘以每頁的列數(10)。再強調一次,電腦是從零開始計算的,所以:

第一頁我們將會顯示第 0 – 9 列

第二頁我們將會顯示第 10 – 19 列

……如此類推……

// 從數據庫取資料
$sql = "SELECT id, number FROM numbers LIMIT $offset, $rowsperpage";
$result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR);

現在執行 SQL 查詢讀取資料表,從指定的起始列數開始讀,提取 10 列數據,由於我們的資料表只有 106 列,最後一頁只有六列。

// 若果還有資料的話...
while ($list = mysql_fetch_assoc($result)) {
// 列印資料
echo $list['id'] . " : " . $list['number'] . "
";
} // end while

跟著我們利用一個簡單迴路顯示目前網頁的數據列,這裏沒有 table 或者 div 等花招,因為不是這篇文件的目的。下一步我們將建立導航欄…

// 若果正在顯示第一頁,無需顯示「前一頁」連結
if ($currentpage > 1) {
// 使用 << 連結回到第一頁
echo " << ";
// 前一頁的頁數
$prevpage = $currentpage - 1;
// 使用 < 連結回到前一頁
echo " < ";
} // end if

首 先要做的是製作一些類似「<< <」的東西,「 echo=”” p=””>

// 顯示的頁數範圍
$range = 3;
// 顯示當前分頁鄰近的分頁頁數
for ($x = (($currentpage - $range) - 1); $x < (($currentpage + $range) + 1); $x++) {
// 如果這是一個正確的頁數...
if (($x > 0) && ($x <= $totalpages)) {
// 如果這一頁等於當前頁數...
if ($x == $currentpage) {
// 不使用連結, 但用高亮度顯示
echo " [$x] ";
// 如果這一頁不是當前頁數...
} else {
// 顯示連結
echo " $x ";
} // end else
} // end if
} // end for

在這裏我們顯示一連串的網頁連結,它的概念是若果我們在第八頁,它會顯示:

我們使用了 $range 代表頁數範圍,它代表目前頁數的左邊和右邊該顯示多少個連結(不是總數量),我們目前在第八頁,$range 是 3,所以 8 的左邊顯示 5 6 7,右邊顯示 9 10 11,迴路從目前頁數減 $range 開始,遍歷至目前頁數加 $range。在迴路中,我們首先檢查當前的頁數是否合法,例如我們正在第一頁,當然不想連結顯示為 -2 -1 0,下一件事就是檢查要顯示的頁數是否目前的頁數,是的話便把它加粗並用方括號令它突顯出來,我們也無須使它成為連結。若果當前的頁數合法,又不是目前的 頁數,便用這個數字做一個連結,就是這麼簡單。

// 如果不是最後一頁, 顯示跳往下一頁及最後一頁的連結
if ($currentpage != $totalpages) {
// 下一頁的頁數
$nextpage = $currentpage + 1;
// 顯示跳往下一頁的連結
echo " > ";
// 顯示跳往最後一頁的連結
echo " >> ";
} // end if
/****** 完成建立分頁連結 ******/
?>

這 部份負責製作「> >>」連結,方法與製作「<< <」如出一轍,只是方向相反罷了:如果我們不是在最後一頁,便需要製作「> >>」連結,製作「>」只需把目前頁數加一,至於「>>」則連結到 $totalpages 變量的值。

好了,製作分頁就只是這麼多東西,當然你可以加入一些花俏元素,例如加入一些按欄位排序的連結,天空才是你的極限!

祝大家編碼快樂!

PHP 基本分頁技巧 有 “ 7 則迴響 ”

  1. 您好~

    (偷偷說 其實我是網頁設計…@@”,程式設計師他剛熬完夜正在睡覺…)

    我想請教一下…如果是要 “下五頁”
    判斷式該怎麼寫呢?

    抱歉…我幾乎不會寫程式,但我想知道如果工程師不在的時候我該怎麼改寫頁碼 > <"

    謝謝~

    1. 可以把第 52 行改為 $range = 5; 這樣便會顯示當前頁的前後五頁的連結。

迴響已關閉。