Thuật toán phân trang với PHP và MySQL
Trên freetuts.net đã có hai bài viết về thuật toán phân trang rồi nhưng những bài đó sử dụng kiến thức của lập trình hướng đối tượng nên rất khó tiếp cận với những bạn mới học PHP, vì vậy trong bài này mình sẽ hướng dẫn các bạn phân trang sử dụng PHP căn bản kết hợp với MySQL. Lưu ý là trong thuật toán này sẽ không có giới hạn số trang.
1. Ý tưởng thuật toán phân trang
Trường hợp 1: Phân trang cho bảng tin tức và tổng số records là 1000. Yêu cầu phân trang với mỗi trang 10 records.
Với trường hợp này ta có tổng số trang là 1000 / 10 = 100
. Nhưng vấn đề là làm thế nào để truy vấn lấy ra các records tương ứng với mỗi trang? Để giải quyết vấn đề này thì ta sử dụng LIMIT trong MySQL. Như vậy câu truy vấn cho từng trang sẽ như sau:
- Trang 01: SELECT * FROM NEWS LIMIT 0, 10
- Trang 02: SELECT * FROM NEWS LIMIT 10, 10
- Trang 03: SELECT * FROM NEWS LIMIT 20, 10
- Trang 04: SELECT * FROM NEWS LIMIT 30, 10
Trường hợp tổng số trang là số lẻ và không chia hết cho số records trên một trang thì ta phải làm tròn phép chia ở cận trên.
Bài viết này được đăng tại [free tuts .net]
Ví dụ: Tổng số records là 22, số records trên một trang là 10 thì tổng số trang là 22/10 = 2.2
, làm tròn lên sẽ là 3
.
Bây giờ là làm thế nào để xác định các con số màu đỏ ở các câu truy vấn trên? Các con số màu đỏ ta gọi là start.
Định nghĩa:
- total_record: tổng số records
- current_page: trang hiện tại
- limit: số records hiển thị trên mỗi trang
- start: record bắt đầu trong câu lệnh SQL
Và đây là công thức tính start:
- Trang 01: start =
(1 - 1) * 10
= 0, tương đương với(current_page - 1) * limit
. - Trang 02: start =
(2 - 1) * 10
= 10, tương đương với(current_page - 1) * limit
. - Trang 03: start =
(3 - 1) * 10
= 20, tương đương với(current_page - 1) * limit
. - Trang 04: start =
(4 - 1) * 10
= 30, tương đương với(current_page - 1) * limit
.
Như vậy thuật toán chung để tính start đó là: start = (current_page - 1) * limit
.
2. Hướng dẫn phân trang bằng PHP
Từ suy luận ở trên ta thấy để phân trang thì phải biết các tham số:
- Tổng số records: Ta dùng lệnh count trong MySQL.
- Trang hiện tại: Dựa vào tham số page trên URL.
- Số records trong mỗi trang: Tham số do coder tự truyền vào.
Để rõ hơn thì ta sẽ làm một ví dụ sử dụng MySQL và PHP để phân trang luôn.
Tạo database:
Bạn tạo một database tên là paging_example
và sau đó chạy câu lệnh SQL sau:
CREATE TABLE IF NOT EXISTS `news` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=46 ;
Câu lệnh này sẽ tạo một table tên là news
. Tiếp theo ta insert vào một số dữ liệu để chạy demo.
INSERT INTO `news` (`id`, `title`) VALUES (1, 'Title 1'), (2, 'Title 2'), (3, 'Title 3'), (4, 'Title 4'), (5, 'Title 5'), (6, 'Title 6'), (7, 'Title 7'), (8, 'Title 8'), (9, 'Title 9'), (10, 'Title 10'), (11, 'Title 11'), (12, 'Title 12'), (13, 'Title 13'), (14, 'Title 14'), (15, 'Title 15'), (16, 'Title 16'), (17, 'Title 17'), (18, 'Title 18'), (19, 'Title 19'), (20, 'Title 20'), (21, 'Title 21'), (22, 'Title 22'), (23, 'Title 23'), (24, 'Title 24'), (25, 'Title 25'), (26, 'Title 26'), (27, 'Title 27'), (28, 'Title 28'), (29, 'Title 29'), (30, 'Title 30'), (31, 'Title 31'), (32, 'Title 32'), (33, 'Title 33'), (34, 'Title 34'), (35, 'Title 35'), (36, 'Title 36'), (37, 'Title 37'), (38, 'Title 38'), (39, 'Title 39'), (40, 'Title 40'), (41, 'Title 41'), (42, 'Title 42'), (43, 'Title 43'), (44, 'Title 44'), (45, 'Title 45');
Tạo layout:
Bạn tạo một file tên là index.php
và sau đó copy đoạn mã HTML dưới đây:
<!DOCTYPE html> <html> <head> <title>Ví dụ phân trang trong PHP và MySQL</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <?php // PHẦN XỬ LÝ PHP ?> <div> <?php // PHẦN HIỂN THỊ TIN TỨC ?> </div> <div class="pagination"> <?php // PHẦN HIỂN THỊ PHÂN TRANG ?> </div> </body> </html>
Trong layout mình có chia làm 3 phần như sau:
- PHẦN XỬ LÝ PHP
- PHẦN HIỂN THỊ TIN TỨC
- PHẦN HIỂN THỊ PHÂN TRANG
Bây giờ ta code cho từng phần nhé.
Phần xử lý PHP: Phần này xử lý truy vấn CSDL và thuật toán phân trang, phần này khá quan trọng bởi nó tính toán các thông số phân trang và khởi tạo các biến sử dụng cho các phần còn lại.
// PHẦN XỬ LÝ PHP // BƯỚC 1: KẾT NỐI CSDL $conn = mysqli_connect('localhost', 'root', 'vertrigo', 'paging_example'); // BƯỚC 2: TÌM TỔNG SỐ RECORDS $result = mysqli_query($conn, 'select count(id) as total from news'); $row = mysqli_fetch_assoc($result); $total_records = $row['total']; // BƯỚC 3: TÌM LIMIT VÀ CURRENT_PAGE $current_page = isset($_GET['page']) ? $_GET['page'] : 1; $limit = 10; // BƯỚC 4: TÍNH TOÁN TOTAL_PAGE VÀ START // tổng số trang $total_page = ceil($total_records / $limit); // Giới hạn current_page trong khoảng 1 đến total_page if ($current_page > $total_page){ $current_page = $total_page; } else if ($current_page < 1){ $current_page = 1; } // Tìm Start $start = ($current_page - 1) * $limit; // BƯỚC 5: TRUY VẤN LẤY DANH SÁCH TIN TỨC // Có limit và start rồi thì truy vấn CSDL lấy danh sách tin tức $result = mysqli_query($conn, "SELECT * FROM news LIMIT $start, $limit");
Phần hiển thị tin tức: Lặp danh sách tin tức và in ra, phần này chỉ lặp dữ liệu và in kết quả
// PHẦN HIỂN THỊ TIN TỨC // BƯỚC 6: HIỂN THỊ DANH SÁCH TIN TỨC while ($row = mysqli_fetch_assoc($result)){ echo '<li>' . $row['title'] . '</li>'; }
Phần hiển thị phân trang: Hiển thị mã HTML phân trang, phần này hiển thị các nút phân trang
// PHẦN HIỂN THỊ PHÂN TRANG // BƯỚC 7: HIỂN THỊ PHÂN TRANG // nếu current_page > 1 và total_page > 1 mới hiển thị nút prev if ($current_page > 1 && $total_page > 1){ echo '<a href="index.php?page='.($current_page-1).'">Prev</a> | '; } // Lặp khoảng giữa for ($i = 1; $i <= $total_page; $i++){ // Nếu là trang hiện tại thì hiển thị thẻ span // ngược lại hiển thị thẻ a if ($i == $current_page){ echo '<span>'.$i.'</span> | '; } else{ echo '<a href="index.php?page='.$i.'">'.$i.'</a> | '; } } // nếu current_page < $total_page và total_page > 1 mới hiển thị nút prev if ($current_page < $total_page && $total_page > 1){ echo '<a href="index.php?page='.($current_page+1).'">Next</a> | '; }
Và đây là toàn bộ file index.php
.
<!DOCTYPE html> <html> <head> <title>Ví dụ phân trang trong PHP và MySQL</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <?php // PHẦN XỬ LÝ PHP // BƯỚC 1: KẾT NỐI CSDL $conn = mysqli_connect('localhost', 'root', 'vertrigo', 'paging_example'); // BƯỚC 2: TÌM TỔNG SỐ RECORDS $result = mysqli_query($conn, 'select count(id) as total from news'); $row = mysqli_fetch_assoc($result); $total_records = $row['total']; // BƯỚC 3: TÌM LIMIT VÀ CURRENT_PAGE $current_page = isset($_GET['page']) ? $_GET['page'] : 1; $limit = 10; // BƯỚC 4: TÍNH TOÁN TOTAL_PAGE VÀ START // tổng số trang $total_page = ceil($total_records / $limit); // Giới hạn current_page trong khoảng 1 đến total_page if ($current_page > $total_page){ $current_page = $total_page; } else if ($current_page < 1){ $current_page = 1; } // Tìm Start $start = ($current_page - 1) * $limit; // BƯỚC 5: TRUY VẤN LẤY DANH SÁCH TIN TỨC // Có limit và start rồi thì truy vấn CSDL lấy danh sách tin tức $result = mysqli_query($conn, "SELECT * FROM news LIMIT $start, $limit"); ?> <div> <?php // PHẦN HIỂN THỊ TIN TỨC // BƯỚC 6: HIỂN THỊ DANH SÁCH TIN TỨC while ($row = mysqli_fetch_assoc($result)){ echo '<li>' . $row['title'] . '</li>'; } ?> </div> <div class="pagination"> <?php // PHẦN HIỂN THỊ PHÂN TRANG // BƯỚC 7: HIỂN THỊ PHÂN TRANG // nếu current_page > 1 và total_page > 1 mới hiển thị nút prev if ($current_page > 1 && $total_page > 1){ echo '<a href="index.php?page='.($current_page-1).'">Prev</a> | '; } // Lặp khoảng giữa for ($i = 1; $i <= $total_page; $i++){ // Nếu là trang hiện tại thì hiển thị thẻ span // ngược lại hiển thị thẻ a if ($i == $current_page){ echo '<span>'.$i.'</span> | '; } else{ echo '<a href="index.php?page='.$i.'">'.$i.'</a> | '; } } // nếu current_page < $total_page và total_page > 1 mới hiển thị nút prev if ($current_page < $total_page && $total_page > 1){ echo '<a href="index.php?page='.($current_page+1).'">Next</a> | '; } ?> </div> </body> </html>
3. Lời kết
Như vậy mình đã hướng dẫn các bạn phân trang đơn giản bằng PHP và MySQL, bài này dành cho newbie nên sẽ không bị giới hạn số trang. Ví dụ bạn có 1000 trang thì nó sẽ hiển thị 1000 button phân trang luôn. Như ở freetuts bạn thấy nếu mình ở trang 10 thì nó sẽ xuất hiện khoảng từ trang số 6 đến trang số 14.