Menu de tree de compilation PHP / MySQL

Estou tentando criar uma tree de menu de lista não-orempada do meu database em PHP e MySQL.

Eu tenho uma série de objects de página que eu estou retornando do db. Cada object de página tem o atributo parent_id, que está definido como nulo se não tiver um pai. Veja como são os objects da página:

page object id title parent_id 

Se possível, eu não gostaria de fazê-lo recursivamente e só atingi o database uma vez, já que vou construir o menu em quase todos os pedidos. Eu quero criar uma function que eu possa simplesmente passar na minha matriz de objects e retornará a lista html.

Eu gosto da solução @ mario, e melhorou com a prevenção do excesso

    . Eu simplesmente recomendaria fazer uma ORDER BY em sua consulta SQL para obter o menu na ordem desejada (pode até recomendar uma coluna de peso / seqüência ser adicionada ao esquema.

    Configuração de dados:

     $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), ); 

    Manipulação:

     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 = "
      "; foreach ($rows as $row) { if ($row['parent_id'] == $parent){ $result.= "
    • {$row['title']}"; if (has_children($rows,$row['id'])) $result.= build_menu($rows,$row['id']); $result.= "
    • "; } } $result.= "
    "; return $result; } echo build_menu($menu);

    Saída:

     
    • Menu 1
      • Sub 1.1
      • Sub 1.2
      • Sub 1.3
    • Menu 2
      • Sub 2.1
        • Sub Sub 2.1.1
      • Sub 2.2
    • Menu 3

    Eu acabei indo com esta solução ( referência ping-bas ):

     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 (ie, 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[] = '
      '; 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 ) . '
    '; $this->html[] = str_repeat( "\t", ( count( $parent_stack ) + 1 ) * 2 - 1 ) . ''; } 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
  • %3$s', $tab, // %1$s = tabulation $option['value']['link'], // https://stackoverflow.com/questions/4413776/php-mysql-build-tree-menu/%2$s = link (URL) $option['value']['title'] // %3$s = title ); $this->html[] = $tab . "\t" . ''; return implode( "\r\n", $this->html ); } }
  • Dados de exemplo:

     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); 

    Uso:

     $menu = new MenuBuilder(); echo '
    ' . htmlentities( $menu->get_menu_html() ) . '';

    Em vez de consultar o database de forma recursiva, você pode simplesmente retirar todas as inputs e tornar a function de saída recursiva. Muitas vezes é tão trivial quanto:

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

    É importante aninhar

      s em

    • . Ou apenas use HTML e deixe de fechar o fechamento.

      Na verdade, isso imprime muitos

        s, então eu verificaria a existência de subníveis e evitaria imprimir diretamente.