【问题标题】:PHP / MySQL build tree menuPHP / MySQL 构建树形菜单
【发布时间】:2010-12-10 22:14:37
【问题描述】:

我正在尝试从我的数据库中的 PHP 和 MySQL 构建一个未排序的列表菜单树。

我有一组从数据库返回的页面对象。每个页面对象都有 parent_id 属性,如果它没有父级,则设置为 null。页面对象如下所示:

page object
  id
  title
  parent_id

如果可能的话,我不想递归地执行它并且只访问数据库一次,因为我将在几乎每个请求上构建菜单。我想创建一个函数,我可以将我的对象数组传递给它,它将返回 html 列表。

【问题讨论】:

    标签: php mysql tree


    【解决方案1】:

    我喜欢@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>

    【讨论】:

    • 这没有为我输出正确的结果。我更新了它以使用对象而不是数组,但这不应该改变任何东西。
    • print_r 您传递的数组,并确保它与我展示的相似。我假设您将执行查询,并将每个 ..._fetch_array 添加到列表中并使用它(或重新创建一个模仿我提供的项目)。
    【解决方案2】:

    您可以只提取所有条目并使输出函数递归,而不是递归地查询数据库。它通常是微不足道的:

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

    &lt;ul&gt;s 嵌套到&lt;li&gt; 中很重要。或者只使用 HTML 并省略结束 &lt;/li&gt;

    实际上这会打印太多&lt;ul&gt;s,所以我会检查是否存在子级别并避免直接打印。

    【讨论】:

      【解决方案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>';
      

      【讨论】:

      • 我的 pastebin 在这里结束了......我在 7 年后承认它......很好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-08
      • 2013-03-12
      • 2010-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多