Olá pessoal, vamos falar sobre menu de níveis infinitos em PHP efetuando somente UMA consulta ao banco de dados. Diferente, né? Pois é. Normalmente para fazer este tipo de menu faz-se uma consulta de todos os registros e dentro do laço que exibe os dados pega-se todos os registros que tenham como idPai o mesmo id deste registro.
Agora, imagina o problema. 20 categorias, cada categoria tendo 5 sub categorias, e cada sub categoria tendo mais 5 sub sub categorias. Quantas consultas ao banco de dados teremos a cada acesso da página? Resposta: 101. Agora imagina 5 pessoas acessando ao mesmo tempo... 505 consultas para cada página carregada. Aí o seu host vai reclamar e você não vai saber o que tem de errado.
Ok, problema exposto... Agora, vamos à solução.
A modelagem do banco de dados não tem segredo.
CREATE TABLE IF NOT EXISTS `menu` (
`menuId` INT NOT NULL AUTO_INCREMENT ,
`menuNome` VARCHAR(45) NOT NULL ,
`menuIdPai` INT NOT NULL DEFAULT 0 ,
`menuLink` VARCHAR(45) NULL ,
PRIMARY KEY (`menuId`) )
ENGINE = InnoDB

Eu gosto de utilizar os campos iniciados pelo nome da tabela. Facilita ao executar as consultas, pois nunca vou ter conflito de nomes e facilita também ao executar Joins. Em outro artigo falaremos mais sobre este assunto.
Vamos incluir alguns valores, apenas para exemplificar.
INSERT INTO menu VALUES
(1, 'A Empresa', 0, 'empresa.php'),
(2, 'Sobre Nós', 1, 'sobre.php'),
(3, 'Objetivos', 1, 'objetivos.php'),
(4, 'Contato', 0, 'contato.php'),
(5, 'Produtos', 0, 'produtos.php'),
(6, 'Informática', 5, 'categoria.php?cat=informatica'),
(7, 'Missão da Empresa', 2, 'missao.php'),
(8, 'Visão da Empresa', 2, 'visao.php'),
(9, 'Televisão', 5, 'categoria.php?cat=televisao'),
(10, 'Computadores', 6, 'subcategoria.php?sub=computadores'),
(11, 'Monitores', 6, 'subcategoria.php?sub=monitores');
Agora, vamos ao código PHP. Utilizaremos a classe MySQLi para realizar a consulta, como descrito abaixo:
<?php
$mysqli = new mysqli('localhost','root','','helpmaster');$query = $mysqli->query('SELECT * FROM menu ORDER BY menuIdPai');
while($row = $query->fetch_object())
Até aqui sem maiores segredos, correto? A função fetch_object() retornará, a cada iteração do while, um objeto contendo como nome das propriedades os campos da tabela. Dentro do laço vamos salvar o resultado em um Array, para criarmos o menu, utilizando o código abaixo:
while($row = $query->fetch_object())
{
$menuItens[$row->menuIdPai][$row->menuId] = array('link' => $row->menuLink,'name' => $row->menuNome);
}
Ao final deste laço, teremos o seguinte array.
Array
(
[0] => Array
(
[1] => Array
(
[link] => empresa.php
[name] => A Empresa
)
[4] => Array
(
[link] => contato.php
[name] => Contato
)
[5] => Array
(
[link] => produtos.php
[name] => Produtos
)
)
[1] => Array
(
[2] => Array
(
[link] => sobre.php
[name] => Sobre Nós
)
[3] => Array
(
[link] => objetivos.php
[name] => Objetivos
)
)
[2] => Array
(
[7] => Array
(
[link] => missao.php
[name] => Missão da Empresa
)
[8] => Array
(
[link] => visao.php
[name] => Visão da Empresa
)
)
[5] => Array
(
[9] => Array
(
[link] => categoria.php?cat=televisao
[name] => Televisão
)
[6] => Array
(
[link] => categoria.php?cat=informatica
[name] => Informática
)
)
[6] => Array
(
[10] => Array
(
[link] => subcategoria.php?sub=computadores
[name] => Computadores
)
[11] => Array
(
[link] => subcategoria.php?sub=monitores
[name] => Monitores
)
)
)
Onde:
Agora, vamos exibir o menu utilizando <ul><li>. Para isto, vamos utilizar a função imprimeMenuInfinito. Vamos criá-la passo a passo abaixo.
function imprimeMenuInfinito( array $menuTotal , $idPai = 0 )
{
Aqui recebemos por parâmetro o array criado na consulta ao banco de dados e o idPai, sendo que o valor padrão é 0 (para o menu inicial).
echo '<ul>';
Aqui abrimos a ul do menu principal, mas ainda não fechamos
foreach( $menuTotal[$idPai] as $idMenu => $menuItem)
{
Aqui vamos iniciar a iteração do array. Para cada item do menu que tenha como idPai o idPai passado como parâmetro, vamos ter a chave do array na $idMenu e o seu valor em $menuItem (que também é um array).
echo '<li><a href="',$menuItem['link'],'">',$menuItem['name'],'</a>';
Aqui vamos imprimir o item do menu, utilizando o array menuItem. Veja que ainda não fechamos a li.
if( isset( $menuTotal[$idMenu] ) ) imprimeMenuInfinito( $menuTotal , $idMenu );
Aqui está o segredo. O que verificamos. Se existe o índice do item do menu que está sendo impresso nesta iteração, significa que este menu tem filhos. Então, chamamos novamente a função, passando o menu completo e o id do menu atual (que, como vimos, é o array pai, utilizado para imprimir o item do menu).
echo '</li>';
}
echo '</ul>';
Aqui fechamos a li do item do menu, fechamos o laço e o ul do menu principal.
Para chamar a função, utilizamos o código assim:
imprimeMenuInfinito($menuItens);
Veja que ao chamarmos a função, não adicionamos o parâmetro idPai. Porque isto? Porque definimos o idPai para o primeiro nível como sendo 0, que é o valor padrão da função.
Após executar este código, teremos o menu formado com o seguinte HTML gerado:
<ul><li><a href="empresa.php">A Empresa</a><ul><li><a href="sobre.php">Sobre Nós</a><ul><li><a href="missao.php">Missão da Empresa</a></li><li><a href="visao.php">Visão da Empresa</a></li></ul></li><li><a href="objetivos.php">Objetivos</a></li></ul></li><li><a href="contato.php">Contato</a></li><li><a href="produtos.php">Produtos</a><ul><li><a href="categoria.php?cat=televisao">Televisão</a></li><li><a href="categoria.php?cat=informatica">Informática</a><ul><li><a href="subcategoria.php?sub=computadores">Computadores</a></li><li><a href="subcategoria.php?sub=monitores">Monitores</a></li></ul></li></ul></li></ul>
Ficou feio este HTML, né? Tudo na mesma linha, sem indentação. Vamos arrumar, colocando as quebras de linha e a indentação correta?
function imprimeMenuInfinito( array $menuTotal , $idPai = 0, $nivel = 0 )
{
Para definir em que nível o menu está, adicionamos um parâmetro à função.
echo str_repeat( "\t" , $nivel ),'<ul>',PHP_EOL;
Aqui utilizamos o \t, que adiciona um tab ao código fonte, sendo repetido de acordo com a $nivel. Ao final da linha, utilizamos a constante PHP_EOL, que significa End Of Line. Esta constante exibe a quebra de linha de acordo com o sistema operacional onde está sendo executado o código. Se for em servidor Windows, \r\n e no Linux \n. Então, utilizando o PHP_EOL, seu código fica portável, pois pode ser executado tanto em servidor Win como em servidor Linux que terá o mesmo comportamento.
echo str_repeat( "\t" , $nivel + 1 ),'<li><a href="',$menuItem['link'],'">',$menuItem['name'],'</a>',PHP_EOL;
Dentro do foreach, fazemos a mesma coisa que fizemos antes, mas adicionamos 1 ao nível do menu.
if( isset( $menuTotal[$idMenu] ) ) imprimeMenuInfinito( $menuTotal , $idMenu , $nivel + 2);
Ao chamarmos recursivamente a função, adicionamos 2 ao nível atual.
echo str_repeat( "\t" , $nivel + 1 ),'</li>',PHP_EOL;
No fechamento do li utilizamos o mesmo nível do li anterior.
echo str_repeat( "\t" , $nivel ),'</ul>',PHP_EOL;
No fechamento do ul utilizamos também o mesmo nível do anterior.
Com este código, o HTML do menu ficou assim:
<ul>
<li><a href="empresa.php">A Empresa</a>
<ul>
<li><a href="sobre.php">Sobre Nós</a>
<ul>
<li><a href="missao.php">Missão da Empresa</a>
</li>
<li><a href="visao.php">Visão da Empresa</a>
</li>
</ul>
</li>
<li><a href="objetivos.php">Objetivos</a>
</li>
</ul>
</li>
<li><a href="contato.php">Contato</a>
</li>
<li><a href="produtos.php">Produtos</a>
<ul>
<li><a href="categoria.php?cat=televisao">Televisão</a>
</li>
<li><a href="categoria.php?cat=informatica">Informática</a>
<ul>
<li><a href="subcategoria.php?sub=computadores">Computadores</a>
</li>
<li><a href="subcategoria.php?sub=monitores">Monitores</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Legal né? Então, o código completo da função ficou assim.
<?php
/**
* Função imprimeMenuInfinito - Função recursiva utilizada para imprimir
* menu com submenus em níveis infinitos.
*
* @author MatiasRezende - contato@matiasrezende.com.br
* @license http://creativecommons.org/licenses/by-sa/2.5/br/
* @param array $menuTotal - Array do menu a ser impresso
* @param $idPai - Id da categoria pai
*/
function imprimeMenuInfinito( array $menuTotal , $idPai = 0, $nivel = 0 )
{
// abrimos a ul do menu principal
echo str_repeat( "\t" , $nivel ),'<ul>',PHP_EOL;
// itera o array de acordo com o idPai passado como parâmetro na função
foreach( $menuTotal[$idPai] as $idMenu => $menuItem)
{
// imprime o item do menu
echo str_repeat( "\t" , $nivel + 1 ),'<li><a href="',$menuItem['link'],'">',$menuItem['name'],'</a>',PHP_EOL;
// se o menu desta iteração tiver submenus, chama novamente a função
if( isset( $menuTotal[$idMenu] ) ) imprimeMenuInfinito( $menuTotal , $idMenu , $nivel + 2);
// fecha o li do item do menu
echo str_repeat( "\t" , $nivel + 1 ),'</li>',PHP_EOL;
}
// fecha o ul do menu principal
echo str_repeat( "\t" , $nivel ),'</ul>',PHP_EOL;
}
$mysqli = new mysqli('localhost','root','','imasters');
$query = $mysqli->query('SELECT * FROM menu ORDER BY menuIdPai');
while($row = $query->fetch_object())
{
$menuItens[$row->menuIdPai][$row->menuId] = array('link' => $row->menuLink,'name' => $row->menuNome);
}
imprimeMenuInfinito($menuItens);
Espero que tenham gostado. Qualquer coisa, comentem!
domingo,17 de janeiro de 2010
1003 Vizualizações
Carlos Eduardo Matias Rezende, ou só Matias Rezende. Programador PHP, Mysql, jQuery, CSS, HTML e Zend Framework. Trabalho também como consultor de empresas nas áreas financeira, operacional e comercial, além de ser programador Web. Trabalho com programação desde fevereiro de 2008, já tendo desenvolvido vários projetos, desde simples sites até ERP completos, todos em PHP. Moderador do Fórum Imasters, na seção de PHP.
www.HelpMasters.com.br | Todos os direitos reservados