代码之家  ›  专栏  ›  技术社区  ›  Starx

如何通过php和mysql构建无限级菜单

  •  8
  • Starx  · 技术社区  · 14 年前

    好吧,为了建立我的菜单,我使用了类似这样的数据库结构

      2  Services                  0
      3  Photo Gallery             0
      4  Home                      0
      5  Feedback                  0
      6  FAQs                      0
      7  News & Events             0
      8  Testimonials              0
     81  FACN                      0
     83  Organisation Structure   81
     84  Constitution             81
     85  Council                  81
     86  IFAWPCA                  81
     87  Services                 81
     88  Publications             81
    

    要为现有的子菜单分配另一个子菜单,我只需将其父子菜单的ID分配为其父字段的值。 父级0表示顶部菜单

    现在在另一个子菜单中创建子菜单没有问题

    这就是我获取顶部菜单的子菜单的方法

    <ul class="topmenu">
        <? $list = $obj -> childmenu($parentid); 
            //this list contains the array of submenu under $parendid
            foreach($list as $menu) {
                extract($menu);
                echo '<li><a href="#">'.$name.'</a></li>';
            }
        ?>
    </ul>
    

    我想做的是。

    我想检查新菜单是否有其他子菜单

    我想继续检查直到它搜索到所有可用的子菜单

    我想把它的子菜单显示在它特定的列表项中,就像这样

    <ul>       
           <li><a href="#">Home</a>
            <ul class="submenu">
               ........ <!-- Its sub menu -->
               </ul>
           </li>
    </ul>
    
    8 回复  |  直到 7 年前
        1
  •  11
  •   nickf    14 年前

    为此需要使用递归函数。 从技术上讲,有几种方法可以做到这一点,但递归实际上是最好的选择 .

    以下是其工作原理的基本要点:

    function drawMenu ($listOfItems) {
        echo "<ul>";
        foreach ($listOfItems as $item) {
            echo "<li>" . $item->name;
            if ($item->hasChildren()) {
                drawMenu($item->getChildren()); // here is the recursion
            }
            echo "</li>";
        }
        echo "</ul>";
    }
    

    的性质和方法 $item 只是一些例子,我将让您根据需要来实现这些功能,但我认为它可以传达信息。

        2
  •  19
  •   ULazdins    13 年前

    以下是“开发人员友好”版本的 一个查询 , 无递归 “解决这个问题。

    SQL :

    SELECT id, parent_id, title, link, position FROM menu_item ORDER BY parent_id, position;
    

    PHP :

    $html = '';
    $parent = 0;
    $parent_stack = array();
    
    // $items contains the results of the SQL query
    $children = array();
    foreach ( $items as $item )
        $children[$item['parent_id']][] = $item;
    
    while ( ( $option = each( $children[$parent] ) ) || ( $parent > 0 ) )
    {
        if ( !empty( $option ) )
        {
            // 1) The item contains children:
            // store current parent in the stack, and update current parent
            if ( !empty( $children[$option['value']['id']] ) )
            {
                $html .= '<li>' . $option['value']['title'] . '</li>';
                $html .= '<ul>'; 
                array_push( $parent_stack, $parent );
                $parent = $option['value']['id'];
            }
            // 2) The item does not contain children
            else
                $html .= '<li>' . $option['value']['title'] . '</li>';
        }
        // 3) Current parent has no more children:
        // jump back to the previous menu level
        else
        {
            $html .= '</ul>';
            $parent = array_pop( $parent_stack );
        }
    }
    
    // At this point, the HTML is already built
    echo $html;
    

    您只需要了解$parent_stack变量的用法。

    它是一个“后进先出”的堆栈(后进先出),维基百科文章中的图片价值一千字: http://en.wikipedia.org/wiki/LIFO_%28computing%29

    当菜单选项有子选项时,我们将其父ID存储在堆栈中:

    array_push( $parent_stack, $parent );
    

    然后,我们立即更新$parent,使其成为当前菜单选项id:

    $parent = $option['value']['id'];
    

    循环其所有子选项后,我们可以返回到上一个级别:

    $parent = array_pop( $parent_stack );
    

    这就是为什么我们将父ID存储在堆栈中的原因!

    我的建议是:仔细考虑上面的代码片段,并理解它。

    欢迎提问!

    我在这种方法中看到的一个优点是,它消除了进入无限循环的风险,当使用递归时,这种情况可能发生。

        3
  •  16
  •   Starx    13 年前

    使用与您类似的数据库结构,可以使用 单一查询 没有递归 .

    是-我重复一遍:

    • 一个查询
    • 无递归

    这是我一直使用的方法。

    将代码粘贴到此处-完全正常:

    http://pastebin.com/GAFvSew4

    跳到第67行,查看有趣的部分(“get_menu_html”)。

    主回路从85号线开始。

    有五个“可自定义”的HTML代码段:

    1. 菜单包装打开(第83行)
    2. 菜单包装关闭(第122行)
    3. 打开儿童的菜单项(第100行)
    4. 儿童关闭的菜单项(第92行)
    5. 不带子菜单项(第113行)

    (如果我不担心的话,代码可能会更干净 制表 )

    脚本末尾提供了用于创建和填充示例数据库的SQL。

    你可以试着让我们知道你的想法。

        4
  •  4
  •   Alistair Evans    14 年前

    我建议您研究一下预先排序的树遍历。有一篇关于这个问题的文章在:

    Managing Hierarchical Data in MySQL

    实际上,您将每个页面作为一个“节点”。每个节点都有对其父节点的引用。当您更改节点的布局(添加子节点、移动节点等)时,您将重新计算每个节点的“左”和“右”值(上面的文章详细解释了这一点,并链接到PHP中的源代码)。您最终得到的是能够非常快速地确定给定节点是任何其他节点的直接子节点还是间接子节点,以及获取给定节点的所有子节点。

        5
  •  2
  •   Jon Black    13 年前

    alt text http://i.imagehost.org/0934/product_hier.jpg http://pastie.org/969286

    drop table if exists product;
    
    create table product
    (
    prod_id smallint unsigned not null auto_increment primary key,
    name varchar(255) not null,
    parent_id smallint unsigned null,
    key (parent_id)
    )engine = innodb;
    
    
    insert into product (name, parent_id) values
    ('Products',null), 
       ('Systems & Bundles',1), 
       ('Components',1), 
          ('Processors',3), 
          ('Motherboards',3), 
            ('AMD',5), 
            ('Intel',5), 
               ('Intel LGA1366',7);
    
    
    delimiter ;
    
    drop procedure if exists product_hier;
    
    delimiter #
    
    create procedure product_hier
    (
    in p_prod_id smallint unsigned
    )
    begin
    
    declare v_done tinyint unsigned default 0;
    declare v_depth smallint unsigned default 0;
    
    create temporary table hier(
     parent_id smallint unsigned, 
     prod_id smallint unsigned, 
     depth smallint unsigned default 0
    )engine = memory;
    
    insert into hier select parent_id, prod_id, v_depth from product where prod_id = p_prod_id;
    
    /* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
    
    create temporary table tmp engine=memory select * from hier;
    
    while not v_done do
    
        if exists( select 1 from product p inner join hier on p.parent_id = hier.prod_id and hier.depth = v_depth) then
    
            insert into hier 
                select p.parent_id, p.prod_id,  v_depth + 1 from product p 
                inner join tmp on p.parent_id = tmp.prod_id and tmp.depth = v_depth;
    
            set v_depth = v_depth + 1;          
    
            truncate table tmp;
            insert into tmp select * from hier where depth = v_depth;
    
        else
            set v_done = 1;
        end if;
    
    end while;
    
    select 
     p.prod_id,
     p.name as prod_name,
     b.prod_id as parent_prod_id,
     b.name as parent_prod_name,
     hier.depth
    from 
     hier
    inner join product p on hier.prod_id = p.prod_id
    inner join product b on hier.parent_id = b.prod_id
    order by
     hier.depth, hier.prod_id;
    
    drop temporary table if exists hier;
    drop temporary table if exists tmp;
    
    end #
    
    delimiter ;
    
    
    call product_hier(3);
    
    call product_hier(5);
    
        6
  •  1
  •   Carlos Arturo Alaniz    11 年前

    http://pastebin.com/ariBn3pE

    您需要使用递归,但我的方法不同,我创建了一个类来单独处理每个菜单,然后查询结果,并根据父对象对单个对象中的每个元素进行分组,按级别组织,然后将所有对象合并为一个…检查Pastebin的完整代码

        7
  •  0
  •   bimbom22    14 年前

    我将使用递归函数。

    我知道这和你的代码不完全一样,但是我认为如果你理解递归的话,你可以得到一般的概念。如果您不理解递归,请签出 http://en.wikipedia.org/wiki/Recursion_(computer_science)

    $list = new List();
    
    function print_menu($list) {
    
        echo '<ul>';
        foreach($list as $item) {
            echo '<li><a href="#">' . $item->name . '</a>';
            if($item->has_child) {
                print_menu($item);
            }
            echo '</li>';
        }
        echo '</ul>';
    }
    
        8
  •  0
  •   Timothy Justin T.    8 年前

    我是这样发现的,使用yii框架。

    $children = array();
    
    foreach($model as $k => $item){
        if(empty($item->cn_id_menu_padre))
            $children[$item->cn_id] = $item->attributes;
        else
            $children[$item->cn_id_menu_padre]['hijos'][] = $item->attributes;
    }
    
    foreach($children as $k=>$child){
        if(array_key_exists('hijos',$child))
        {
            echo 'li y dentro ul<br>';
            foreach($child['hijos'] as $hijo){
                echo 'li<br>';
            }
        }
        else
            echo 'li<br>';
    }
    

    如果您还需要一个级别,可以将子数组中的另一个级别设置为 hijos_de_hijos 然后在if语句中进行比较。

    哦,当然,比较一下 cn_id_menu_padre 为空,数据库中的值应为 null .