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

Sta*_*arx 8 php mysql menu

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

  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>
Run Code Online (Sandbox Code Playgroud)

我想做的是.

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

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

我想在其特定列表项中显示其子菜单,如下所示

<ul>       
       <li><a href="#">Home</a>
        <ul class="submenu">
           ........ <!-- Its sub menu -->
           </ul>
       </li>
</ul>
Run Code Online (Sandbox Code Playgroud)

J. *_*uni 20

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

SQL:

SELECT id, parent_id, title, link, position FROM menu_item ORDER BY parent_id, position;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

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

这是一个"LIFO"堆栈(Last In,First Out) - 维基百科文章中的图片价值千言万语:http://en.wikipedia.org/wiki/LIFO_%28computing%29

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

array_push( $parent_stack, $parent );
Run Code Online (Sandbox Code Playgroud)

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

$parent = $option['value']['id'];
Run Code Online (Sandbox Code Playgroud)

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

$parent = array_pop( $parent_stack );
Run Code Online (Sandbox Code Playgroud)

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

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

欢迎提出问题!

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


J. *_*uni 16

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

是的 - 我会重复:

  • 一个查询
  • 没有复发

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

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

http://pastebin.com/GAFvSew4

跳转到第67行以查看有趣的部分("get_menu_html").

主循环从第85行开始.

有五个"可自定义"的HTML片段:

  1. 菜单包装开口(第83行)
  2. 菜单包装器关闭(第122行)
  3. 带孩子的菜单项(第100行)
  4. 关闭孩子的菜单项(第92行)
  5. 没有孩子的菜单项(第113行)

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

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

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

  • @ J.Bruni:一个出色的解决方案!谢谢你! (3认同)

nic*_*ckf 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>";
}
Run Code Online (Sandbox Code Playgroud)

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

  • 虽然您的方法是有效的,但这种递归可能会导致大量的数据库命中(对于hasChildren的每次调用,可能会有一次,对于getChildren,可能会有另一次).如果这些是存储在内存中的项目,没问题,但是每次打到数据库都会减慢速度. (3认同)
  • @Kazar - 这完全取决于实现。您可以在一个查询中完成所有操作,然后将结果解析为一棵树,或者查看使用嵌套集 http://dev.mysql.com/tech-resources/articles/hierarchical-data.html - 不过,因为这是一个菜单,不太可能有那么多值得担心的递归级别。 (2认同)

Kaz*_*zar 5

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

在 MySQL 中管理分层数据

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