2011-05-11 86 views
0

下面是一個表結構,我有:PHP遞歸幫助需要創建一個樹形結構

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'])我想。否則,您每次都調用該函數完全相同。 – superbly 2011-05-11 08:37:07

+0

德哦,感謝您指出,發佈這個答案,我會選擇它,我怎麼會錯過它?我需要咖啡 – Kumar 2011-05-11 08:57:08

+0

有時也會發生在我身上:D – superbly 2011-05-11 09:17:08

回答

2

我認爲你必須添加的ID作爲參數的函數調用。

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

否則您每次調用該函數都完全一樣。

4

是的,還有更好的方法。所謂修改的預置樹遍歷算法。你可以通過使用這個搜索來找到大量的信息,我也確定在堆棧溢出。

好處是,您可以使用1個查詢來獲取整個子樹。選擇速度會很快,但修改更重。

+0

爲了我的天真和愚蠢,我原諒了我的天真和愚蠢,但是我從來沒有聽說過這個消息,如果我已經在文本中閱讀過它,我已經像很多其他東西一樣錯過了 – Kumar 2011-05-11 08:55:13

+0

有時也稱爲[嵌套](http://en.wikipedia.org/wiki/Nested_set_model)。 – 2011-11-15 14:50:03

0

行:

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

必須是:

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

耶! @ shifty指出這一點,我慚愧:( – Kumar 2011-05-11 08:58:17

0
<?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); 
    } 
} 





?> 
1

這是最好的例子。這是第一個答案的正確形式。

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

使用基於關係數據庫的父/子關係構建使用樹結構的菜單非常麻煩。對於樹結構,關係數據庫是可怕的。他們要求您編寫大量業務邏輯,以便以可讀格式表示數據。要更新帶有附加功能的菜單,需要添加到遞歸循環中......它會變得非常多毛,這取決於您希望菜單變得多複雜。更何況你最終會想要緩存整個事情,因爲在負載較重的情況下,循環在計算上變得非常昂貴。想想看,如果你有5個頂級菜單項,2個孩子和每個孩子都有n孩子自己,你將會運行16個SQL語句。

我可以提供另一種解決方案:JSON。我曾經有過這樣的菜單表,現在我只是將它的JSON表示存儲在SQL數據庫中(儘管這可能會緩存在內存/文件系統中)。 JSON菜單在空間方面更加緊湊,只需閱讀並且不需要擺弄父母和孩子ID就符合邏輯。它使用PHP(json_encode/decode)將菜單轉換爲本地數組效果更好。和Javascript一樣重要,例如,如果您正在進行Ajax調用,可以在您的應用程序中對菜單進行重新排序。分層樹結構是JSON擅長的。它也消除了需要保持跟蹤你的「menuorder」的(因爲在本質上指定的數組順序)

一個例子菜單格式如下:

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

正如你可以看到它提供了額外的功能真的很容易,比如與菜單項相關的「角色」。添加這種功能不需要新的遞歸代碼或對SQL模式的更改。它的確更加靈活。

所以,沒有真正回答這個問題,但希望提供一些建議/見解,我覺得這是對這個問題的更好的解決方案。