Đệ quy chuyên mục đa cấp trong PHP toàn tập
Sẵn có câu hỏi trên trang qa.freetuts.net về cách hiển thị chuyên mục đa cấp nên mình làm một bài kết hợp các kiểu đệ quy hiển thị chuyên mục đa cấp luôn.
Chúng ta có một số định dạng thường sử dụng khi đệ quy như sau:
- Đệ quy hiển thị với table trong quản lý admin
- Đệ quy hiển thị với thẻ select trong quản lý admin
- Đệ quy với thẻ ul li trong trường hợp hiển thị menu
Tuy mình phân ra thành nhiều loại nhưng thực chất chúng ta chỉ dùng một thuật toán duy nhất đó là giải thuật đệ quy. Và giải thuật này chúng ta áp dụng cho cả menu đa cấp lẫn chuyên mục đa cấp.
Trước tiên chúng ta cần xây dựng một database để demo.
Bài viết này được đăng tại [free tuts .net]
1. Tạo database lưu trữ chuyên mục đa cấp
Để đơn giản thì mình sẽ xây dựng một database gồm ba fields chính đó là id
, title
và parent_id
. Trong đó id
là khóa chính và tăng tự động, title
là tiêu đề của chuyên mục và parent_id
là khóa ngoại trỏ đển chuyên mục cha. Trường hợp với cấp cao nhất thì sẽ có parent_id = 0
.
Bạn tạo một CSDL demo
và một bảng categories
bằng cách chạy đoạn mã SQL sau.
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; CREATE DATABASE `demo` ; USE DATABASE `demo`; CREATE TABLE IF NOT EXISTS `categories` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `parent_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=11 ; INSERT INTO `categories` (`id`, `title`, `parent_id`) VALUES (1, 'PHP', 0), (2, 'JAVASCRIPT', 0), (3, 'Codeigniter', 1), (4, 'Phalcon', 1), (5, 'Tutorials', 3), (6, 'AngularJS', 2), (7, 'jQuery', 2), (8, 'Basic', 5), (9, 'Advance', 5), (10, 'Course', 3);
Kết quả của database này chúng ta có một cây Categoy được phân cấp như sau:
2. Phân tích ý tưởng lấy dữ liệu để đệ quy
Hiện nay trên mạng có nhiều bài viết hướng dẫn đệ quy nhưng mình thấy đa số những bài viết đó sử dụng PHP thuần và mỗi lần đệ quy sẽ tốn một câu truy vấn nên dẫn đến chương trình chạy rất chậm.
Hầu hết các framework hiện nay đều hỗ trợ truy vấn database dạng Active Record và kết quả nó trả về một mảng được đánh số thứ tự từ 0 -> n-1
, vì vậy trong bài này thay vì đệ quy và lặp kết quả thông qua vòng lặp while và hàm mysqli_fetch_assoc
thì ta sẽ thực hiện qua 2 bước:
- Bước 1: Lấy danh sách chuyên mục từ datavase và đưa vào một mảng, và đây chính là đoạn code php xử lý bước 1:
// BƯỚC 1: LẤY DANH SÁCH CATEGORIES $conn = mysqli_connect('localhost', 'root', 'vertrigo', 'demo'); $sql = 'SELECT * FROM categories'; $result = mysqli_query($conn, $sql); $categories = array(); while ($row = mysqli_fetch_assoc($result)){ $categories[] = $row; }
- Bước 2: Đệ quy mảng các chuyên mục có được ở bước 1. Tai bước này để tối ưu thì ta nên xóa phần tử đã được hiển thị bằng cách sử dụng hàm
unset
.
// BƯỚC 2: HÀM ĐỆ QUY HIỂN THỊ CATEGORIES function showCategories($categories, $parent_id = 0, $char = '') { foreach ($categories as $key => $item) { // Nếu là chuyên mục con thì hiển thị if ($item['parent_id'] == $parent_id) { // Xử lý hiển thị chuyên mục // ..... // ..... // ..... // Xóa chuyên mục đã lặp unset($categories[$key]); // Tiếp tục đệ quy để tìm chuyên mục con của chuyên mục đang lặp showCategories($categories, $item['id'], $char.'|---'); } } }
Nhiệm vụ tiếp theo của bước 2 sẽ được trình bày ở các phần tiếp theo dưới đây.
3. Đệ quy chuyên mục đa cấp thẻ table trong admin
Chúng ta có 3 bước, trong đó có 2 bước mình đã bình bày ở trên rồi nên chỉ đăng code kèm comment thôi nhé.
// BƯỚC 1: LẤY DANH SÁCH CATEGORIES $conn = mysqli_connect('localhost', 'root', 'vertrigo', 'demo'); $sql = 'SELECT * FROM categories'; $result = mysqli_query($conn, $sql); $categories = array(); while ($row = mysqli_fetch_assoc($result)){ $categories[] = $row; } // BƯỚC 2: HÀM ĐỆ QUY HIỂN THỊ CATEGORIES function showCategories($categories, $parent_id = 0, $char = '') { foreach ($categories as $key => $item) { // Nếu là chuyên mục con thì hiển thị if ($item['parent_id'] == $parent_id) { echo '<tr>'; echo '<td>'; echo $char . $item['title']; echo '</td>'; echo '</tr>'; // Xóa chuyên mục đã lặp unset($categories[$key]); // Tiếp tục đệ quy để tìm chuyên mục con của chuyên mục đang lặp showCategories($categories, $item['id'], $char.'|---'); } } }
Bước tiếp theo là gọi hàm đệ quy hiển thị danh sách chuyên mục.
<table border="1" cellspacing="0" cellpadding="5"> <tr> <td><strong>Chuyên mục</strong></td> </tr> <?php showCategories($categories); ?> </table>
Và hình ảnh sau là thành quả của chúng ta:
3. Đệ quy chuyên mục đa cấp thẻ select option
Đối với dạng này ta chỉ cần chỉnh một chút trong hàm hiển thị chuyên mục là được.
// BƯỚC 2: HÀM ĐỆ QUY HIỂN THỊ CATEGORIES function showCategories($categories, $parent_id = 0, $char = '') { foreach ($categories as $key => $item) { // Nếu là chuyên mục con thì hiển thị if ($item['parent_id'] == $parent_id) { echo '<option value="'.$item[$key].'">'; echo $char . $item['title']; echo '</option>'; // Xóa chuyên mục đã lặp unset($categories[$key]); // Tiếp tục đệ quy để tìm chuyên mục con của chuyên mục đang lặp showCategories($categories, $item['id'], $char.'|---'); } } }
Và đây là code hiển thị thẻ select chuyên mục:
<select> <?php showCategories($categories); ?> </select>
Và đây là thành quả:
4. Đệ quy chuyên mục đa cấp với thẻ UL và LI
Với chức năng này hơi khó một chút xíu vì bạn sẽ phải xử lý để lúc hiển thị không thị dư thẻ UL
. Để thực hiện được điều này ta phải chia bước 2 thành hai bước nhỏ như sau:
- Bước 2.1: Lấy danh sách chuyên mục con
- Bước 2.2: Kiểm tra nếu có chuyên mục con thì hiển thị thẻ
UL
, đồng thời lặp và hiển thị thẻLI
.
// BƯỚC 2: HÀM ĐỆ QUY HIỂN THỊ CATEGORIES function showCategories($categories, $parent_id = 0, $char = '') { // BƯỚC 2.1: LẤY DANH SÁCH CATE CON $cate_child = array(); foreach ($categories as $key => $item) { // Nếu là chuyên mục con thì hiển thị if ($item['parent_id'] == $parent_id) { $cate_child[] = $item; unset($categories[$key]); } } // BƯỚC 2.2: HIỂN THỊ DANH SÁCH CHUYÊN MỤC CON NẾU CÓ if ($cate_child) { echo '<ul>'; foreach ($cate_child as $key => $item) { // Hiển thị tiêu đề chuyên mục echo '<li>'.$item['title']; // Tiếp tục đệ quy để tìm chuyên mục con của chuyên mục đang lặp showCategories($categories, $item['id'], $char.'|---'); echo '</li>'; } echo '</ul>'; } }
Và đây là cách sử dụng:
<?php showCategories($categories); ?>
Và đây là thành quả:
Còn một vấn đề nữa đó là xử lý hiển thị class cho thẻ UL - LI
cha và các thẻ UL - LI
con, để xử lý trường hợp này thì ta thêm một tham số $stt
có giá trị ban đầu = 0, và sau mỗi lần đệ quy sẽ tăng $stt
lên 1 đơn vị. Lúc này để kiểm tra cấp để bổ sung class thì chỉ cần kiểm tra biến $stt
.
// BƯỚC 2: HÀM ĐỆ QUY HIỂN THỊ CATEGORIES function showCategories($categories, $parent_id = 0, $char = '', $stt = 0) { // BƯỚC 2.1: LẤY DANH SÁCH CATE CON $cate_child = array(); foreach ($categories as $key => $item) { // Nếu là chuyên mục con thì hiển thị if ($item['parent_id'] == $parent_id) { $cate_child[] = $item; unset($categories[$key]); } } // BƯỚC 2.2: HIỂN THỊ DANH SÁCH CHUYÊN MỤC CON NẾU CÓ if ($cate_child) { if ($stt == 0){ // là cấp 1 } else if ($stt == 1){ // là cấp 2 } else if ($stt == 2){ // là cấp 3 } echo '<ul>'; foreach ($cate_child as $key => $item) { // Hiển thị tiêu đề chuyên mục echo '<li>'.$item['title']; // Tiếp tục đệ quy để tìm chuyên mục con của chuyên mục đang lặp showCategories($categories, $item['id'], $char.'|---', ++$stt); echo '</li>'; } echo '</ul>'; } }
Quá đơn giản phải không các bạn.
5. Lời kết
Những cách đệ quy trên mình chỉ xử lý ở dạng thô sơ nên để đáp ứng với giao diện của bạn thì hãy bổ sung các class, id cho phù hợp nhé.
Đây là cách mình chia sẻ nên nếu bạn có cách nào hay hơn thì hãy chia sẻ thêm để mọi người cùng học hỏi nhé.
Chúc bạn thành công!