如何通过PHP和mysql构建无限级别的菜单

时间:2010-05-20 07:48:46

标签: php mysql menu

好吧,为了构建我的菜单我的菜单我使用类似db的类似结构

  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 个答案:

答案 0 :(得分:19)

这是针对此问题的“一个查询无递归”解决方案的“开发人员友好”版本。

<强> 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变量的用法。

这是一个“LIFO”堆栈(Last In,First Out) - 维基百科文章中的图片价值千言万语: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存储在堆栈中的原因!

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

欢迎提出问题!

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

答案 1 :(得分:16)

使用与您类似的数据库结构,可以使用单个查询无递归构建整个HTML菜单。

是的 - 我会重复:

  • ONE QUERY
  • NO RECURSION

这是我一直用自己的方法。

在此处粘贴代码 - 功能齐全:

http://pastebin.com/GAFvSew4

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

主循环从第85行开始。

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

  1. 菜单包装器打开(第83行)
  2. 菜单包装器关闭(第122行)
  3. 子项打开的菜单项(第100行)
  4. 关闭孩子的菜单项(第92行)
  5. 没有孩子的菜单项(第113行)
  6. (如果我不担心制表,代码可能会更清晰。)

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

    您可以尝试让我们知道您的想法。

答案 2 :(得分:11)

您需要使用递归函数。 从技术上讲,有几种方法可以做到,但递归确实是最好的选择

以下是它如何运作的基本要点:

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的属性和方法只是示例,我会根据您的需要实现这些属性和方法,但我认为它会传达信息。

答案 3 :(得分:4)

我建议您查看预先排序的树遍历。有一个关于这个问题的文章:

Managing Hierarchical Data in MySQL

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

答案 4 :(得分:2)

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);

答案 5 :(得分:1)

http://pastebin.com/ariBn3pE

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

答案 6 :(得分:0)

我会使用递归函数。

我知道这与您的代码不完全相同,但我认为如果您理解递归,您可以获得一般概念。如果您不理解递归,请查看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>';
}

答案 7 :(得分:0)

我找到了这种方式,使用Yii Framework。

$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