创建树结构所需的 PHP 递归帮助


PHP recursion help needed to create a tree structure

这是我有一个表结构:

CREATE TABLE menu (
  menuid int(11) NOT NULL AUTO_INCREMENT,
  menuname varchar(100) NOT NULL DEFAULT '',
  menulink varchar(100) NOT NULL DEFAULT '',
  menuparentId int(11) NOT NULL DEFAULT '0',
  menuhasChild smallint(1) NOT NULL DEFAULT '0',
  menustatus smallint(1) NOT NULL DEFAULT '1',
  menuorder int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (menuid)
)

我正在使用递归函数从中创建菜单结构并在此处失败:

function categoriesTree($id=0){    
    $s = "SELECT * FROM menu  WHERE menuparentId = '".$id."' 
        ORDER BY menuorder, menuid ";
    $rid = $this->db->query($s)->result_array();
    $treeArray = array();
    foreach($rid as $row){
        $treeArray[$row['menuid']] = $row;
        if($row['menuhasChild']==1){
            $treeArray[$row['menuid']] = $this->categoriesTree(); //results in Fatal error: Maximum function nesting level of '100' reached, aborting!
        }
    }
 retrun $treeArray;
 }

此方法是 CodeIgniter 模型类中模型的一部分。有没有更好的方法来创建树?

是的,有一个更好的方法。所谓修正的预序树遍历算法。您可以通过谷歌搜索找到大量信息,我相信堆栈溢出也是如此。

好处是您只需使用 1 个查询即可获取整个子树。选择会很快,但修改更重。

我认为您必须在函数调用中添加id作为参数。

$this->categoriesTree($row['menuid']) 

否则,每次

调用函数的方式完全相同。

这是最好的例子。这是第一个答案的更正形式。

 function categoriesTree($id=0) {
    $s = "SELECT * FROM design_menu  WHERE menuparentId = '" . $id . "' 
    ORDER BY menuorder, menuid ";
    $rid = $this->db->query($s)->result_array();
    $treeArray = array();
    foreach ($rid as $row) {
        $treeArray[$row['menuid']] = $row;
        if ($row['menuhasChild'] == 1) {
            $treeArray[$row['menuname']] = $this->categoriesTree($row['menuid']); //results in Fatal error: Maximum function nesting level of '100' reached, aborting!
        }
    }
    return $treeArray;
}

行 :

$treeArray[$row['menuid']] = $this->categoriesTree();

必须是 :

$treeArray[$row['menuid']] = $this->categoriesTree($row['menuid']);
<?php
require_once ROOT_PATH . '/lib/dao/MySQLClass.php';
require_once ROOT_PATH . '/lib/confs/Conf.php';
/**
 * Generate HTML for multi-dimensional menu from MySQL database
 * with ONE QUERY and WITHOUT RECURSION
 * @author J. Bruni
 */
//print_r($_SESSION['symfony/user/sfUser/culture']);die;
class MenuBuilder
{
    /**
     * MySQL connection
     */
    var $conn;
    /**
     * Menu items
     */
    var $items = array();
    /**
     * HTML contents
     */
    var $html  = array();
        //var $culture = $_SESSION['symfony/user/sfUser/culture'];
        var $culture;
        var $columnName;
    /**
     * Create MySQL connection
     */
    function MenuBuilder()
    {
        $conf = new Conf();
            $db=new MySQLClass($conf);
           $this->conn = mysql_connect($db->myHost .':'.$db->myHostPort, $db->userName, $db->userPassword);
           $this->culture=$_SESSION['language'];
    }
    /**
     * Perform MySQL query and return all results
     */
    function fetch_assoc_all( $sql )
    {
                if($this->culture=="en"){
                $this->columnName='sm_mnuitem_name';
                }else{
                    $this->columnName='sm_mnuitem_name_'.$this->culture;
                }
                //die(print_r($_SESSION));
                if($_SESSION['user']=="USR001"){
                $query="SELECT sm_mnuitem_id, sm_mnuitem_parent, ".$this->columnName.", sm_mnuitem_webpage_url, sm_mnuitem_position FROM hs_hr_sm_mnuitem ORDER BY sm_mnuitem_parent, sm_mnuitem_position;";
                }
                else{
                    $query="select * from hs_hr_sm_mnuitem m left join hs_hr_sm_mnucapability c on m.sm_mnuitem_id=c.sm_mnuitem_id left join hs_hr_users u on u.sm_capability_id=c.sm_capability_id where u.id='".$_SESSION['user']."' ORDER BY m.sm_mnuitem_parent, m.sm_mnuitem_position;";
                }
        //$result = mysql_query("SELECT sm_mnuitem_id, sm_mnuitem_parent, ".$this->columnName.", sm_mnuitem_webpage_url, sm_mnuitem_position FROM hs_hr_sm_mnuitem ORDER BY sm_mnuitem_parent, sm_mnuitem_position;",$this->conn);
                $result = mysql_query($query,$this->conn);
        if ( !$result ){
            return false;
                }
        $assoc_all = array();
        while( $fetch = mysql_fetch_assoc( $result ) ){
            $assoc_all[] = $fetch;
                }
                //die(print_r($assoc_all));
        mysql_free_result( $result );
        return $assoc_all;
    }
    /**
     * Get all menu items from database
     */
    function get_menu_items()
    {
        // Change the field names and the table name in the query below to match tour needs
        $sql = 'SELECT sm_mnuitem_id, sm_mnuitem_parent, sm_mnuitem_name, sm_mnuitem_webpage_url, sm_mnuitem_position FROM hs_hr_sm_mnuitem ORDER BY s_mnuitem_parent, sm_mnuitem_position;';
        return $this->fetch_assoc_all( $sql );
    }
    /**
     * Build the HTML for the menu
     */
    function get_menu_html( $root_id = 0 )
    {
        $this->html  = array();
        $this->items = $this->get_menu_items();
                //print_r($this->items);die("");
        foreach ( $this->items as $item )
            $children[$item['sm_mnuitem_parent']][] = $item;
        // loop will be false if the root has no children (i.e., an empty menu!)
        $loop = !empty( $children[$root_id] );
        // initializing $parent as the root
        $parent = $root_id;
        $parent_stack = array();
        // HTML wrapper for the menu (open)
                //$this->html[] = '<div>';
        $this->html[] = '<ul id="qm0" class="qmmc">';
        while ( $loop && ( ( $option = each( $children[$parent] ) ) || ( $parent > $root_id ) ) )
        {
            if ( $option === false )
            {
                $parent = array_pop( $parent_stack );
                // HTML for menu item containing childrens (close)
                $this->html[] = str_repeat( "'t", ( count( $parent_stack ) + 1 ) * 2 ) . '</ul>';
                $this->html[] = str_repeat( "'t", ( count( $parent_stack ) + 1 ) * 2 - 1 ) . '</li>';
            }
            elseif ( !empty( $children[$option['value']['sm_mnuitem_id']] ) )
            {
                $tab = str_repeat( "'t", ( count( $parent_stack ) + 1 ) * 2 - 1 );
                // HTML for menu item containing childrens (open)
                $url="";
                if($option['value']['sm_mnuitem_webpage_url']=="#"){
                    $url="javascript:void(0);";
                }else{
                    $url=$option['value']['sm_mnuitem_webpage_url'];
                }        
                $this->html[] = sprintf(
                    '%1$s<li><a class="qmparent" href="%2$s">%3$s</a>',
                    $tab,   // %1$s = tabulation
                    //$option['value']['sm_mnuitem_webpage_url'],   // %2$s = link (URL)
                    $url,
                    $option['value'][$this->columnName]   // %3$s = title
                );
                $this->html[] = $tab . "'t" . '<ul>';
                array_push( $parent_stack, $option['value']['sm_mnuitem_parent'] );
                $parent = $option['value']['sm_mnuitem_id'];
            }
            else{
                // HTML for menu item with no children (aka "leaf")
                             if($_SESSION['user']!="USR001"){
                            if($option['value']['sm_mnuitem_webpage_url']!="#"){
                $this->html[] = sprintf(
                    '%1$s<li><a target="rightMenu" href="%2$s">%3$s</a></li>',
                    str_repeat( "'t", ( count( $parent_stack ) + 1 ) * 2 - 1 ),   // %1$s = tabulation
                    $option['value']['sm_mnuitem_webpage_url'],   // %2$s = link (URL)
                    $option['value'][$this->columnName]   // %3$s = title
                );
                            }
                             }else{
                                 $this->html[] = sprintf(
                    '%1$s<li><a target="rightMenu" href="%2$s">%3$s</a></li>',
                    str_repeat( "'t", ( count( $parent_stack ) + 1 ) * 2 - 1 ),   // %1$s = tabulation
                    $option['value']['sm_mnuitem_webpage_url'],   // %2$s = link (URL)
                    $option['value'][$this->columnName]   // %3$s = title
                );
                             }
                        }
        }
        // HTML wrapper for the menu (close)
        $this->html[] = '</ul>';
                //$this->html[] = '</div>';
        return implode( "'r'n", $this->html );
    }
}


?>

使用基于关系数据库的父/子关系的树结构构建菜单非常麻烦。关系数据库对于树结构来说是可怕的。它们要求您编写大量业务逻辑,只是为了以可读的格式表示数据。要使用附加功能更新菜单需要添加到该递归循环中......它可能会变得非常毛茸茸,具体取决于您希望菜单变得多么复杂。更不用说你最终会想要缓存整个东西,因为在重负载下循环在计算上变得非常昂贵。想一想,如果你有 5 个顶级菜单项,2 个子项,每个子项自己有 n 个子项,你将运行 16 个 SQL 语句。

我可以提供另一种解决方案:JSON。我曾经有这样的菜单表,现在我只是在SQL数据库中存储它的JSON表示形式(尽管即使这样也可以缓存在内存/文件系统中)。JSON菜单在空间方面要紧凑得多,简单阅读是合乎逻辑的,不需要摆弄父ID和子ID。它与PHP(json_encode/解码)将菜单转换为本机数组时效果更好。以及Javascript,这很重要,例如,如果你正在执行ajax调用来重新排序应用程序中的菜单。分层树结构是JSON擅长的。它还消除了跟踪"菜单顺序"的需要(因为数组顺序本质上是指定的)

菜单格式示例如下:

{
["en": "Home", "fr": "Accueil"],
["en": "Settings", "fr": "Paramètres", "child": 
    {
        ["en": "Email", "fr": "Email", "role": "EmailUser"]
    }
}

如您所见,它非常容易地提供了附加功能,例如绑定到菜单项的"角色"。添加此类功能不需要新的递归代码或更改 SQL 架构。它真的更加灵活。

所以,不是真正回答这个问题,但希望提供一些建议/见解,说明我认为是这个问题的更好解决方案。