Đệ 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.

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, titleparent_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!

Hãy để lại link bài viết gốc khi chia sẻ bài viết này, mình sẽ report DMCA với những website lấy nội dung mà không để nguồn hoặc copy bài với số lượng lớn.

Nguồn: freetuts.net

Profile photo of adminTheHalfHeart

TheHalfHeart

Có sở thích viết tuts nên đã từng tham gia viết ở một số diễn đàn, đến năm 2014 mới có điều kiện sáng lập ra freetuts.net. Sinh năm 90 và có 1 vợ 2 con, thích ca hát và lập trình.

ĐĂNG BÌNH LUẬN: Đăng câu hỏi trên Group Facebook để được hỗ trợ nhanh nhất.