作為一個網頁開發人員,你經常要用容易閱讀的格式把數據顯示給用戶,舉例說你要從數據庫讀取一份雇員名單,並在網頁上羅列出來,若果名單只有區區十 多人,用一個簡單迴圈把所有人列印出來便好了,不是很簡單嗎?但若果你有五十名雇員又如何呢?一百人?一千人?把這麼多人一口氣羅列出來顯然不是一個好主意,Crayon Violent 在 PHP Freaks 寫了一篇教學文件,介紹如何透過 PHP 來實作分頁。
同一時間從數據庫扯出所有數據,你的用戶可能會一面呆等一面納悶,系統究竟發什麼神經老是沒有輸出?千呼萬喚有結果了,得到的竟然是一本完完整整的「三國演義」,在一頁版面上列印出來!據說古人的確是把不論長短的文章寫在一匹絹布上,一種好像稱為卷軸的東西,詳情我也不清楚,但廿一世紀的你不會也這麼做吧?
聰明的做法是把一份長長的名單分割成很多小段,每次只從數據庫提取及顯示一段,這可以大幅降低伺服器的工作量,也提升網頁的下載速度,用戶也比較容易消化屏幕上的資料,不會一下子被你弄至消化不良,這種做法就是分頁。
一個基本的分頁函式初看起來可能又長又可怖,但其實你只要閉上眼睛,深吸一口氣,然後閱讀每一個分段,便會發現它其實是很簡單的東西。根據 Crayon 多年來在論壇上幫助別人的經驗,人們對分頁的最大困難在於搞不清這種技術叫作什麼!既然我們搞定了最困難的部份,餘下的便易如反掌了,不是嗎?
首先,我們要在數據庫中建立一個資料表,並放進一些數據,這裡不會討論安裝數據庫、建立資料表等細節,若果你不清楚這些東西,這份教學文件並不適合你。資料表的內容並不重要,若果你已經有一個滿載數據的資料表可作為測試用途,歡迎把以下程式中資料表和欄位的名稱更改,為了方便說明,文中的資料表名稱 是「numbers」,它有兩個欄位:「number」和「id」,兩者的類型都是 int,其中「id」是自動遞增的。
若果你打算用這些名稱自行建立資料表,不妨用以下的程式片段來產生測試用的數據:
| PHP | | copy code | | ? |
| 01 | <?php |
| 02 | $conn = mysql_connect('localhost','dbusername','dbpassword') or trigger_error("SQL", E_USER_ERROR); |
| 03 | $db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR); |
| 04 | |
| 05 | for ($x = 0; $x < 106; $x++) { |
| 06 | $number = rand(100,999); |
| 07 | $sql = "INSERT INTO numbers (number, id) VALUES ($number, '')"; |
| 08 | $query = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR); |
| 09 | } |
| 10 | ?> |
長話短說:這個程式會產生 106 個隨機的 3 位數,為什麼是 106 個呢?這只是隨便選的,而且,分頁程式每次會顯示 10 筆資料,這樣便可以保證最後一頁少於 10 筆資料。為什麼是 3 位數呢?為什麼最後一頁要顯示 6 筆資料?這些全部都是隨意選擇的,沒有特別的原因,請相信我。
好了,正式開始前我們先看看偉大的分頁程式完整版本,很多教學文件把程式解剖成一小段一小段,讀者們被逼重複多次的複製和貼上,十分令人困擾,但偏偏與本文的宗旨互相矛盾,不是很諷刺嗎?
| PHP | | copy code | | ? |
| 01 | <?php |
| 02 | // 數據庫連結資料 |
| 03 | $conn = mysql_connect('localhost','dbusername','dbpass') or trigger_error("SQL", E_USER_ERROR); |
| 04 | $db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR); |
| 05 | |
| 06 | // 計算資料表中有多少列 |
| 07 | $sql = "SELECT COUNT(*) FROM numbers"; |
| 08 | $result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR); |
| 09 | $r = mysql_fetch_row($result); |
| 10 | $numrows = $r[0]; |
| 11 | |
| 12 | // 每頁顯示的列數 |
| 13 | $rowsperpage = 10; |
| 14 | // 計算總共需要多少頁 |
| 15 | $totalpages = ceil($numrows / $rowsperpage); |
| 16 | |
| 17 | // 取得當前的頁數,或者顯示預設的頁數 |
| 18 | if (isset($_GET['currentpage']) && is_numeric($_GET['currentpage'])) { |
| 19 | // 把變量的類型轉換成 int |
| 20 | $currentpage = (int) $_GET['currentpage']; |
| 21 | } else { |
| 22 | // 預設的頁數 |
| 23 | $currentpage = 1; |
| 24 | } // end if |
| 25 | |
| 26 | // 若過當前的頁數大於頁數總數 |
| 27 | if ($currentpage > $totalpages) { |
| 28 | // 把當前頁數設定為最後一頁 |
| 29 | $currentpage = $totalpages; |
| 30 | } // end if |
| 31 | // 若果當前的頁數小於 1 |
| 32 | if ($currentpage < 1) { |
| 33 | // 把當前頁數設定為 1 |
| 34 | $currentpage = 1; |
| 35 | } // end if |
| 36 | |
| 37 | // 根據當前頁數計算名單的起始位置 |
| 38 | $offset = ($currentpage - 1) * $rowsperpage; |
| 39 | |
| 40 | // 從數據庫取資料 |
| 41 | $sql = "SELECT id, number FROM numbers LIMIT $offset, $rowsperpage"; |
| 42 | $result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR); |
| 43 | |
| 44 | // 若果還有資料的話... |
| 45 | while ($list = mysql_fetch_assoc($result)) { |
| 46 | // 列印資料 |
| 47 | echo $list['id'] . " : " . $list['number'] . "<br />"; |
| 48 | } // end while |
| 49 | |
| 50 | /****** 建立分頁連結 ******/ |
| 51 | // 顯示的頁數範圍 |
| 52 | $range = 3; |
| 53 | |
| 54 | // 若果正在顯示第一頁,無需顯示「前一頁」連結 |
| 55 | if ($currentpage > 1) { |
| 56 | // 使用 << 連結回到第一頁 |
| 57 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=1'><<</a> "; |
| 58 | // 前一頁的頁數 |
| 59 | $prevpage = $currentpage - 1; |
| 60 | // 使用 < 連結回到前一頁 |
| 61 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=$prevpage'><</a> "; |
| 62 | } // end if |
| 63 | |
| 64 | // 顯示當前分頁鄰近的分頁頁數 |
| 65 | for ($x = (($currentpage - $range) - 1); $x < (($currentpage + $range) + 1); $x++) { |
| 66 | // 如果這是一個正確的頁數... |
| 67 | if (($x > 0) && ($x <= $totalpages)) { |
| 68 | // 如果這一頁等於當前頁數... |
| 69 | if ($x == $currentpage) { |
| 70 | // 不使用連結, 但用高亮度顯示 |
| 71 | echo " [<b>$x</b>] "; |
| 72 | // 如果這一頁不是當前頁數... |
| 73 | } else { |
| 74 | // 顯示連結 |
| 75 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=$x'>$x</a> "; |
| 76 | } // end else |
| 77 | } // end if |
| 78 | } // end for |
| 79 | |
| 80 | // 如果不是最後一頁, 顯示跳往下一頁及最後一頁的連結 |
| 81 | if ($currentpage != $totalpages) { |
| 82 | // 下一頁的頁數 |
| 83 | $nextpage = $currentpage + 1; |
| 84 | // 顯示跳往下一頁的連結 |
| 85 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=$nextpage'>></a> "; |
| 86 | // 顯示跳往最後一頁的連結 |
| 87 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=$totalpages'>>></a> "; |
| 88 | } // end if |
| 89 | /****** 完成建立分頁連結 ******/ |
| 90 | ?> |
好了,就是這麼多代碼,它的基本構思是:
- 計算你的資料表有多少列
- 根據每頁你想顯示的列數,計算有多少分頁
- 照出當前的頁數
- 從數據庫提取需要顯示的資料
- 顯示資料
- 製作一些「第一頁」、「前一頁」、當前頁鄰近的分頁、「下一頁」和「最後一頁」的連結
上面的原碼有很多注釋,所以你應該可以弄清楚是怎麼回事,無論如何,下面我們會把它拆解分析,好,繼續前進……
| PHP | | copy code | | ? |
| 1 | // 數據庫連結資料 |
| 2 | $conn = mysql_connect('localhost','dbusername','dbpass') or trigger_error("SQL", E_USER_ERROR); |
| 3 | $db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR); |
以上是幾行很顯然只是用來連接數據庫。
| PHP | | copy code | | ? |
| 1 | // 計算資料表中有多少列 |
| 2 | $sql = "SELECT COUNT(*) FROM numbers"; |
| 3 | $result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR); |
| 4 | $r = mysql_fetch_row($result); |
| 5 | $numrows = $r[0]; |
知道了資料表有多少列,我們才知道有多少分頁,因此,我們執行基本的 count(*) 查詢並把接過儲存在 $numrows。
| PHP | | copy code | | ? |
| 1 | // 每頁顯示的列數 |
| 2 | $rowsperpage = 10; |
| 3 | // 計算總共需要多少頁 |
| 4 | $totalpages = ceil($numrows / $rowsperpage); |
$rowsperpage 是每頁顯示的列數,我們把總列數($numrows)除以每頁的列數($rowsperpage),便知道有多少分頁需要顯示,由於不會有半頁(除非給你的愛犬啃掉了),我們使用 celi() 來計算確實的頁數。
| PHP | | copy code | | ? |
| 1 | // 取得當前的頁數,或者顯示預設的頁數 |
| 2 | if (isset($_GET['currentpage']) && is_numeric($_GET['currentpage'])) { |
| 3 | // 把變量的類型轉換成 int |
| 4 | $currentpage = (int) $_GET['currentpage']; |
| 5 | } else { |
| 6 | // 預設的頁數 |
| 7 | $currentpage = 1; |
| 8 | } // end if |
當你點擊一個分頁的連結時,目標分頁的頁數會透過 GET 方法傳到伺服器,這裡正是要取得這個分頁頁數。由於第一次進入這個程式時並不會傳送這個變量,所以我們必須檢查流覽器是否傳來這個變量,與及它是否一個整數,說不定有人在 URL 手動更改這個變量的數值。
如果接收到這個變量,又是一個數字的話,我們便把它強制轉換為 int(整數)類型,原因是倘若有人在 URL 中把這個變量設定為 9.75,它便會轉換成第 9 分頁,除非你是哈利波特,否則應該很清楚世界上沒有第 9.75 分頁。
如果輸入的不是數字,我們預設為第一頁。
| PHP | | copy code | | ? |
| 01 | // 若過當前的頁數大於總頁數... |
| 02 | if ($currentpage > $totalpages) { |
| 03 | // 把當前頁數設定為最後一頁 |
| 04 | $currentpage = $totalpages; |
| 05 | } // end if |
| 06 | // 若果當前的頁數小於 1... |
| 07 | if ($currentpage < 1) { |
| 08 | // 把當前頁數設定為 1 |
| 09 | $currentpage = 1; |
| 10 | } // end if |
下 一步是檢查這是否一個有效的頁碼,原因是倘若我們的小說只有 400 頁,有一個不滿小說結局,或者不肯相信小說就此結束的人,幻想我們還有一個隱藏起來的第 401 頁,於是直接在 URL 中把頁數更改為 401,那時怎麼辦?所以啊……如果有人輸入一個負數的頁數,或者超過總頁數的數字,我們分別把它預設為 1 和總頁數。
| PHP | | copy code | | ? |
| 1 | // 根據當前頁數計算名單的起始位置 |
| 2 | $offset = ($currentpage - 1) * $rowsperpage; |
我 們總共有 106 筆資料,但每次只顯示 10 筆,所以要計算從哪一筆資料開始提取,例如第 8 頁的起始列是第 80 筆資料,不過事實上應該是第 70 筆,因為電腦總愛從零開始計算,因此我們把目前的頁數(8)減去一,乘以每頁的列數(10)。再強調一次,電腦是從零開始計算的,所以:
第一頁我們將會顯示第 0 – 9 列
第二頁我們將會顯示第 10 – 19 列
……如此類推……
| PHP | | copy code | | ? |
| 1 | // 從數據庫取資料 |
| 2 | $sql = "SELECT id, number FROM numbers LIMIT $offset, $rowsperpage"; |
| 3 | $result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR); |
現在執行 SQL 查詢讀取資料表,從指定的起始列數開始讀,提取 10 列數據,由於我們的資料表只有 106 列,最後一頁只有六列。
| PHP | | copy code | | ? |
| 1 | // 若果還有資料的話... |
| 2 | while ($list = mysql_fetch_assoc($result)) { |
| 3 | // 列印資料 |
| 4 | echo $list['id'] . " : " . $list['number'] . "<br />"; |
| 5 | } // end while |
跟著我們利用一個簡單迴路顯示目前網頁的數據列,這裡沒有 table 或者 div 等花招,因為不是這篇文件的目的。下一步我們將建立導航欄…
| PHP | | copy code | | ? |
| 1 | // 若果正在顯示第一頁,無需顯示「前一頁」連結 |
| 2 | if ($currentpage > 1) { |
| 3 | // 使用 << 連結回到第一頁 |
| 4 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=1'><<</a> "; |
| 5 | // 前一頁的頁數 |
| 6 | $prevpage = $currentpage - 1; |
| 7 | // 使用 < 連結回到前一頁 |
| 8 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=$prevpage'><</a> "; |
| 9 | } // end if |
首 先要做的是製作一些類似「<< <」的東西,「 echo="" p="">
| PHP | | copy code | | ? |
| 01 | // 顯示的頁數範圍 |
| 02 | $range = 3; |
| 03 | // 顯示當前分頁鄰近的分頁頁數 |
| 04 | for ($x = (($currentpage - $range) - 1); $x < (($currentpage + $range) + 1); $x++) { |
| 05 | // 如果這是一個正確的頁數... |
| 06 | if (($x > 0) && ($x <= $totalpages)) { |
| 07 | // 如果這一頁等於當前頁數... |
| 08 | if ($x == $currentpage) { |
| 09 | // 不使用連結, 但用高亮度顯示 |
| 10 | echo " [<b>$x</b>] "; |
| 11 | // 如果這一頁不是當前頁數... |
| 12 | } else { |
| 13 | // 顯示連結 |
| 14 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=$x'>$x</a> "; |
| 15 | } // end else |
| 16 | } // end if |
| 17 | } // end for |
在這裡我們顯示一連串的網頁連結,它的概念是若果我們在第八頁,它會顯示:
<< < 5 6 7 [8] 9 10 11 > >>
我們使用了 $range 代表頁數範圍,它代表目前頁數的左邊和右邊該顯示多少個連結(不是總數量),我們目前在第八頁,$range 是 3,所以 8 的左邊顯示 5 6 7,右邊顯示 9 10 11,迴路從目前頁數減 $range 開始,遍歷至目前頁數加 $range。在迴路中,我們首先檢查當前的頁數是否合法,例如我們正在第一頁,當然不想連結顯示為 -2 -1 0,下一件事就是檢查要顯示的頁數是否目前的頁數,是的話便把它加粗並用方括號令它突顯出來,我們也無須使它成為連結。若果當前的頁數合法,又不是目前的 頁數,便用這個數字做一個連結,就是這麼簡單。
| PHP | | copy code | | ? |
| 01 | // 如果不是最後一頁, 顯示跳往下一頁及最後一頁的連結 |
| 02 | if ($currentpage != $totalpages) { |
| 03 | // 下一頁的頁數 |
| 04 | $nextpage = $currentpage + 1; |
| 05 | // 顯示跳往下一頁的連結 |
| 06 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=$nextpage'>></a> "; |
| 07 | // 顯示跳往最後一頁的連結 |
| 08 | echo " <a href='{$_SERVER['PHP_SELF']}?currentpage=$totalpages'>>></a> "; |
| 09 | } // end if |
| 10 | /****** 完成建立分頁連結 ******/ |
| 11 | ?> |
這 部份負責製作「> >>」連結,方法與製作「<< <」如出一轍,只是方向相反罷了:如果我們不是在最後一頁,便需要製作「> >>」連結,製作「>」只需把目前頁數加一,至於「>>」則連結到 $totalpages 變量的值。
好了,製作分頁就只是這麼多東西,當然你可以加入一些花俏元素,例如加入一些按欄位排序的連結,天空才是你的極限!
祝大家編碼快樂!
