2010-12-10 11 views
7

PHPとMySQLで私のデータベースからun-orederedリストメニューツリーを構築しようとしています。PHP/MySQLビルドツリーメニュー

私はデータベースから戻ってくるページオブジェクトの配列を持っています。各ページオブジェクトにはparent_id属性があり、親がない場合はnullに設定されます。私はほとんどすべてのリクエストに応じてメニューを構築するつもりですので、私は、再帰的にそれを行うと、一度だけのデータベースをヒットしないしたい可能であれば

page object 
    id 
    title 
    parent_id 

:ここでは、ページオブジェクトがどのように見えるかです。私はオブジェクトの配列を渡すことができ、htmlリストを返す関数を作成したいと思います。

答えて

14

私は@ marioの解決策が好きであり、それを超過防止のために改良した<ul>。私はちょうどあなたが(でも、スキーマに追加され、重量/シーケンスの欄をお勧めかもしれませんしたい順にメニューを得るためにあなたのSQLクエリにORDER BYを行うことをお勧め

データセットアップ:。

$menu = array(// Presumed to have been coming from a SQL SELECT, populated for demo. 
    array('id'=>1,'title'=>'Menu 1',   'parent_id'=>null), 
    array('id'=>2,'title'=>'Sub 1.1',   'parent_id'=>1), 
    array('id'=>3,'title'=>'Sub 1.2',   'parent_id'=>1), 
    array('id'=>4,'title'=>'Sub 1.3',   'parent_id'=>1), 
    array('id'=>5,'title'=>'Menu 2',   'parent_id'=>null), 
    array('id'=>6,'title'=>'Sub 2.1',   'parent_id'=>5), 
    array('id'=>7,'title'=>'Sub Sub 2.1.1', 'parent_id'=>6), 
    array('id'=>8,'title'=>'Sub 2.2',   'parent_id'=>5), 
    array('id'=>9,'title'=>'Menu 3',   'parent_id'=>null), 
); 

取り扱い:

function has_children($rows,$id) { 
    foreach ($rows as $row) { 
    if ($row['parent_id'] == $id) 
     return true; 
    } 
    return false; 
} 
function build_menu($rows,$parent=0) 
{ 
    $result = "<ul>"; 
    foreach ($rows as $row) 
    { 
    if ($row['parent_id'] == $parent){ 
     $result.= "<li>{$row['title']}"; 
     if (has_children($rows,$row['id'])) 
     $result.= build_menu($rows,$row['id']); 
     $result.= "</li>"; 
    } 
    } 
    $result.= "</ul>"; 

    return $result; 
} 
echo build_menu($menu); 

出力:

<ul> 
 
    <li>Menu 1<ul> 
 
    <li>Sub 1.1</li> 
 
    <li>Sub 1.2</li> 
 
    <li>Sub 1.3</li> 
 
    </ul></li> 
 
    <li>Menu 2<ul> 
 
    <li>Sub 2.1<ul> 
 
     <li>Sub Sub 2.1.1</li> 
 
    </ul></li> 
 
    <li>Sub 2.2</li> 
 
    </ul></li> 
 
    <li>Menu 3</li> 
 
</ul>

+0

これは正しい結果を出力していません私のために。配列の代わりにオブジェクトを使うように更新しましたが、何も変更してはいけません。 – imns

+0

print_rあなたが渡している配列を見て、それが私が示したものと似ていることを確認してください。私はあなたがクエリを実行することを前提にして、それぞれの '..._ fetch_array'をリストに追加し、それを使用します(または私が提供したものを模倣したアイテムを再作成します)。 –

2

データベースを再帰的にクエリする代わりに、すべてのエントリを取り出して出力関数を再帰的にすることができます。それは<li>に巣<ul> Sにのみ重要です

function print_list($array, $parent=0) { 
    print "<ul>"; 
    foreach ($array as $row) { 
     if ($row->parent_id == $parent) { 
      print "<li>$row->title"; 
      print_list($array, $row->id); # recurse 
      print "</li>"; 
    } } 
    print "</ul>"; 
} 

:それは多くの場合のように些細なのです。または、HTMLを使用して、終了</li>を除外してください。

これは実際には<ul>の数が多すぎるので、サブレベルが存在するかどうかをチェックし、直接印刷しないようにします。

3

私は、このソリューション(pastebin reference)と一緒に行くことになった:

<?php 

/** 
* Generate HTML for multi-dimensional menu from MySQL database 
* with ONE QUERY and WITHOUT RECURSION 
* @author J. Bruni 
*/ 
class MenuBuilder 
{ 
    /** 
    * MySQL connection 
    */ 
    var $conn; 

    /** 
    * Menu items 
    */ 
    var $items = array(); 

    /** 
    * HTML contents 
    */ 
    var $html = array(); 

    /** 
    * Create MySQL connection 
    */ 
    function MenuBuilder() 
    { 
     $this->conn = mysql_connect('localhost', 'user', 'pass'); 
     mysql_select_db('example', $this->conn); 
    } 

    /** 
    * Perform MySQL query and return all results 
    */ 
    function fetch_assoc_all($sql) 
    { 
     $result = mysql_query($sql, $this->conn); 

     if (!$result) 
      return false; 

     $assoc_all = array(); 

     while($fetch = mysql_fetch_assoc($result)) 
      $assoc_all[] = $fetch; 

     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 id, parent_id, title, link, position FROM menu_item ORDER BY parent_id, 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(); 

     foreach ($this->items as $item) 
      $children[$item['parent_id']][] = $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[] = '<ul>'; 

     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']['id']])) 
      { 
       $tab = str_repeat("\t", (count($parent_stack) + 1) * 2 - 1); 

       // HTML for menu item containing childrens (open) 
       $this->html[] = sprintf(
        '%1$s<li><a href="%2$s">%3$s</a>', 
        $tab, // %1$s = tabulation 
        $option['value']['link'], // %2$s = link (URL) 
        $option['value']['title'] // %3$s = title 
       ); 
       $this->html[] = $tab . "\t" . '<ul class="submenu">'; 

       array_push($parent_stack, $option['value']['parent_id']); 
       $parent = $option['value']['id']; 
      } 
      else 
       // HTML for menu item with no children (aka "leaf") 
       $this->html[] = sprintf(
        '%1$s<li><a href="%2$s">%3$s</a></li>', 
        str_repeat("\t", (count($parent_stack) + 1) * 2 - 1), // %1$s = tabulation 
        $option['value']['link'], // %2$s = link (URL) 
        $option['value']['title'] // %3$s = title 
       ); 
     } 

     // HTML wrapper for the menu (close) 
     $this->html[] = '</ul>'; 

     return implode("\r\n", $this->html); 
    } 
} 

例データ:

CREATE TABLE `menu_item` (
    `id` int(11) NOT NULL, 
    `title` varchar(75) DEFAULT NULL, 
    `link` varchar(100) DEFAULT NULL, 
    `parent_id` int(11) DEFAULT NULL, 
    `position` int(11) DEFAULT NULL, 
    PRIMARY KEY (`id`) 
); 

INSERT INTO `menu_item` (`id`, `title`, `link`, `parent_id`, `position`) VALUES (1,'1','1.html',0,1); 
INSERT INTO `menu_item` (`id`, `title`, `link`, `parent_id`, `position`) VALUES (2,'2','2.html',0,2); 
INSERT INTO `menu_item` (`id`, `title`, `link`, `parent_id`, `position`) VALUES (3,'11','11.html',1,1); 
INSERT INTO `menu_item` (`id`, `title`, `link`, `parent_id`, `position`) VALUES (4,'12','12.html',1,2); 
INSERT INTO `menu_item` (`id`, `title`, `link`, `parent_id`, `position`) VALUES (5,'21','21.html',2,1); 
INSERT INTO `menu_item` (`id`, `title`, `link`, `parent_id`, `position`) VALUES (6,'22','22.html',2,2); 
INSERT INTO `menu_item` (`id`, `title`, `link`, `parent_id`, `position`) VALUES (7,'3','3.html',0,3); 

使用法:

$menu = new MenuBuilder(); 
echo '<pre>' . htmlentities($menu->get_menu_html()) . '</pŕe>'; 
+2

私のペーストビンはここに終わった...そして私はそれを7年後に認めています...素敵です。 –

+0

@ J.Bruniようこそinterwebs – jQuerybeast