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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2871861/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 07:59:02  来源:igfitidea点击:

How to build unlimited level of menu through PHP and mysql

phpmysqlmenu

提问by Starx

Well, to build my menu my menu I use a db similar structure like this

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

  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

To assign another submenu for existing submenu I simply assign its parent's id as its value of parent field. parent 0 means top menu

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

now there is not problem while creating submenu inside another submenu

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

now this is way I fetch the submenu for the top menu

现在这是我获取顶部菜单的子菜单的方式

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

What I want to do is.

我想做的是。

I want to check if a new menu has other child menu

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

and I want to keep on checking until it searches every child menu that is available

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

and I want to display its child menu inside its particular list item like this

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

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

回答by J. Bruni

Here is a "developer-friendly" version of the "one query, no recursion" solution for this problem.

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

SQL:

查询语句

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

PHP:

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;

You just need to understand the usage of the $parent_stack variable.

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

It is a "LIFO" stack (Last In, First Out) - the image in the Wikipedia article worths a thousand words: http://en.wikipedia.org/wiki/LIFO_%28computing%29

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

When a menu option has sub-options, we store its parent ID in the stack:

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

array_push( $parent_stack, $parent );

And then, we immediately update $parent, making it be the current menu option ID:

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

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

After we looped all its sub-options, we can return back to the previous level:

在我们循环了它的所有子选项之后,我们可以返回到上一级:

$parent = array_pop( $parent_stack );

This is why we stored the parent ID in the stack!

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

My suggestion is: contemplate the code snippet above, and understand it.

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

Questions are welcome!

欢迎提问!

One of the advantages I see in this approach is that it eliminates the risk of entering into an infinite loop, which can happen when recursion is used.

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

回答by J. Bruni

With a database structure like yours, it is possible to build the whole HTML menu with a single queryand without recursion.

使用像您这样的数据库结构,可以使用单个查询构建整个 HTML 菜单,而无需递归

Yes - I will repeat:

是的 - 我会重复:

  • ONE QUERY
  • NO RECURSION
  • 一个查询
  • 无复发

This is the approach I always use myself.

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

Pasted the code here - fully functional:

在这里粘贴代码 - 功能齐全:

http://pastebin.com/GAFvSew4

http://pastebin.com/GAFvSew4

Jump to line 67 to see the interesting part ("get_menu_html").

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

The main loop starts at line 85.

主循环从第 85 行开始。

There are five "customizable" HTML snippets:

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

  1. menu wrapper opening (line 83)
  2. menu wrapper closing (line 122)
  3. menu item with childs opening (line 100)
  4. menu item with childs closing (line 92)
  5. menu item without childs (line 113)
  1. 菜单包装打开(第 83 行)
  2. 菜单包装关闭(第 122 行)
  3. 带孩子的菜单项(第 100 行)
  4. 关闭孩子的菜单项(第 92 行)
  5. 没有子项的菜单项(第 113 行)

(The code could be cleaner if I hadn't worried with tabulation.)

(如果我不担心制表,代码可能会更清晰。)

SQL to create and populate sample database is available at the end of the script.

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

You can try and let us know your thoughts.

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

回答by nickf

You need to use recursive functions for this. Technically, there's a few ways to do it, but recursion is really the best option here.

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

Here's the basic gist of how it would work:

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

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>";
}

The properties and methods of $itemare just examples, and I'll leave it up to you to implement these however you need to, but I think it gets the message across.

的属性和方法$item只是示例,我会根据需要由您来实现这些,但我认为它可以传达信息。

回答by Alistair Evans

I would suggest that you look into pre-ordered tree traversal. There is an article on the issue at:

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

Managing Hierarchical Data in MySQL

在 MySQL 中管理分层数据

Effectively, you take each page as a 'node'. Each node has a reference to it's parent. When you change the layout of the nodes (add a child, move nodes, etc), you recalculate a 'left' and 'right' value for each node (the article above explains this in great detail, with links to source code in php). What you end up with is the ability to very quickly determine if a given node is a direct or indirect child of any other node, as well as get all the child nodes of a given node.

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

回答by Jon Black

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

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

回答by Carlos Arturo Alaniz

http://pastebin.com/ariBn3pE

http://pastebin.com/ariBn3pE

You need to use recursion, but my approach it's different, I created a class to handle each menu individually, then queried for results and group each elements in their individual object according to their parents, organized that by levels, and then merge all the objects into one... check the pastebin for the full code

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

回答by Jose Rivas

I found this way, working with Yii Framework.

我找到了这种方式,使用 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>';
}

In case that you need one more level, you could make another level in children array like hijos_de_hijosand do the comparison then in the if statement.

如果您需要更多级别,您可以在 children 数组中创建另一个级别,hijos_de_hijos然后在 if 语句中进行比较。

Oh, of course, to compare if cn_id_menu_padreis empty, the value in the database should be null.

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

回答by jordanstephens

i would use a recursive function.

我会使用递归函数。

i know this isn't exactly like your code, but I think you can get the general concept if you understand recursion. if you don't understand recursion check out http://en.wikipedia.org/wiki/Recursion_(computer_science)

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