如何通过PHP和MySQLbuild立无限级的菜单

那么,build立我的菜单,我的菜单我使用类似这样的数据库结构

   2服务0
   3照片库0
   4家0
   5反馈0
   6常见问题0
   7新闻与活动0
   8推荐书0
  81 FACN 0
  83组织结构81
  84宪法81
  85理事会81
  86 IFAWPCA 81
  87服务81
  88出版物81

要为现有的子菜单指定另一个子菜单,只需将其父母的ID分配为父母字段的值即可。 父母0表示顶层菜单

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

现在这是我获取顶级菜单的子菜单

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

我想要做的是。

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

我想继续检查,直到它search每个可用的子菜单

我想要像这样在其特定的列表项中显示它的子菜单

 <ul> <li><a href="#">Home</a> <ul class="submenu"> ........ <!-- Its sub menu --> </ul> </li> </ul> 

你需要使用这个recursion函数。 从技术上讲,有几种方法可以做到,但recursion在这里是最好的select

这是如何工作的基本要点:

 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的属性和方法只是一个例子,我会留给你去实现这些,不过你需要的,但是我认为它会得到消息。

这个问题的“ 一个查询没有recursion ”解决scheme的“开发友好”版本。

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_stackvariables的用法。

这是一个“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存储在堆栈中的原因!

我的build议是:考虑上面的代码片断,并理解它。

欢迎提问!

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

通过像你这样的数据库结构,可以用一个查询构build整个HTML菜单,而不用recursion

是的 – 我会重复一遍:

  • 一个查询
  • 没有回报

这是我总是使用自己的方法。

粘贴代码在这里 – function齐全:

http://pastebin.com/GAFvSew4

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

主循环从第85行开始。

有五个“可定制的”HTML片段:

  1. 菜单包装开放(83行)
  2. 菜单包装closures(第122行)
  3. 菜单项与孩子开放(100行)
  4. 菜单项与孩子closures(92行)
  5. 没有孩子的菜单项(第113行)

(如果我不担心列表 ,代码可能更清洁。)

用于创build和填充示例数据库的SQL在脚本的末尾可用。

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

我build议你看看预先定好的树遍历。 有关这个问题的文章:

在MySQL中pipe理分层数据

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

替代文字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); 

http://pastebin.com/ariBn3pE

你需要使用recursion,但我的方法是不同的,我创build了一个类来处理每个菜单,然后查询结果,并根据他们的父母将每个元素分组在每个对象中,然后合并所有对象进入一个…检查完整的代码的pastebin

我会使用recursion函数。

我知道这不完全像你的代码,但我认为如果你理解recursion,你可以得到一般的概念。 如果你不明白recursion,请查看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>'; } 

我发现这个方法,与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这样的子hijos_de_hijos组中再创build一个级别,然后在if语句中进行比较。

哦,当然,比较cn_id_menu_padre是否为空,数据库中的值应该是null