Đệ 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ị phân mục đa cấp nên mình làm một bài phối hợp những kiểu đệ quy hiển thị phân mục đa cấp luôn .

test php

banquyen png

Bài viết này được đăng tại

freetuts.net

, không được copy dưới mọi hình thức.

Chúng ta có 1 số ít đị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 tất cả chúng ta cần thiết kế 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 tất cả chúng ta có một cây Categoy được phân cấp như sau :

de quy chuyen muc da cap png

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 hầu hết 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 '';
                echo '';
                    echo $char. $item['title'];
                echo '';
            echo '';
            
            // 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ị list phân mục .

Chuyên mục

Và hình ảnh sau là thành quả của tất cả chúng ta :

de quy chuyen muc da cap php 1 png

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 ít trong hàm hiển thị phâ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 '';
            
            // 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 phân mục :

Và đây là thành quả :

de quy chuyen muc da cap php 3 png

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 '
    '; foreach ($cate_child as $key => $item) { // Hiển thị tiêu đề chuyên mục echo '
  • '.$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 '
  • '; } echo '
'; } }

Và đây là cách sử dụng :

Và đây là thành quả :

de quy chuyen muc da cap php 2 png

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 '
    '; foreach ($cate_child as $key => $item) { // Hiển thị tiêu đề chuyên mục echo '
  • '.$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 '
  • '; } echo '
'; } }

Quá đơn thuần phải không những 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 san sẻ nên nếu bạn có cách nào hay hơn thì hãy san sẻ thêm để mọi người cùng học hỏi nhé .
Chúc bạn thành công xuất sắc !