Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Olá pessoal...
Hoje vamos falar sobre menu de níveis infinitos em PHP... Mas desta vez, vamos criar o menu efetuando somente UMA consulta ao banco de dados. Normalmente quando se fala em criar este tipo de menu, o que é feito:
Fazendo desta forma, se tivermos 20 categorias, e cada categoria tiver 5 sub categorias, e cada sub categoria tiver mais 5 sub sub categorias, teremos 101 consultas ao banco de dados. Imagina isto no seu site, tendo 5 acessos simultâneos – 505 consultas ao banco de dados a cada página aberta. Conseguiu perceber o problema?Agora, como vamos resolver isto?
Banco de dados sugerido
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
/applications/core/interface/imageproxy/imageproxy.php?img=http://img195.imageshack.us/img195/4517/menun.png&key=b9f99275f46532caca11ce576c13d96af1d62c5dfaac26f74670439a7a82f1bf" alt="Imagem Postada" />
Incluindo alguns valores.
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. Faremos a consulta utilizando a classe MySQLi, como mostro abaixo:
<?php
$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);
}
Ao final deste while, 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
)
)
)Então, para exibirmos o menu, utilizando <ul><li>, utilizaremos a função imprimeMenuInfinito. Vamos explicá-la passo a passo.
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 seguinte código gerado:
E 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>
Perai... Mas este HTML tá muito feio (tudo na mesma linha, sem indentação). Então, vamos indentar ele corretamente e colocar as quebras de linha, para ficar completinho.
function imprimeMenuInfinito( array $menuTotal , $idPai = 0, $nivel = 0 )
{
Adicionamos um parâmetro à função, para definir em que nível ela está.
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;
}echo str_repeat( "\t" , $nivel ),'</ul>',PHP_EOL;
}
$mysqli = new mysqli('localhost','root','','imasters');
$query = $mysqli->query('SELECT * FROM menu ORDER BY menuIdPai');$menuItens[$row->menuIdPai][$row->menuId] = array('link' => $row->menuLink,'name' => $row->menuNome);
}
imprimeMenuInfinito($menuItens);
Espero que tenham gostado.
Carlos Eduardo - Blog OGordo
Eu gosto dela... utilizo a class...
Olá!
Achei muito bacana e já estou testando o mesmo.
Porém, me bateu uma dúvida e não soube como resolvê-la... pensei em utilizá-lo em meu sistema que divide os menus em categorias, porém, as categorias "pai" iriam precisar ficar sem link, ai seria possível alguém dar uma luz de como fazer?
Valeu!
Abs
Qual dos dois está utilizando? A classe do João Batista ou a função que eu fiz?
Carlos Eduardo
A classe que você fez mesmo.
vlw!
Você pode apenas criar um condicional onde, caso a $idPai seja igual a 0 (o valor para o primeiro item) você não adiciona link. Algo assim:
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)
{
// caso seja o primeiro nível, não imprime o link
if($idPai == 0)
echo str_repeat( "\t" , $nivel + 1 ),'<li>',$menuItem['name'],PHP_EOL;
// caso não seja, imprime o link
else
// 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;
}
Era mais ou menos isto que você tinha em mente?
Carlos Eduardo
Bem pessoal, também tem este vídeo esplicativo de como fazer esse tipo de menu utilizando banco de dados e o spry do dreamweaver, pra quem utiliza o dreamweaver é uma boa. Segue:
http://forum.imasters.com.br/public/style_emoticons/default/seta.gif Parte 1 - http://www.becck.com/?sec=visualizarMaterias&id_materia=132&id_coluna=25
http://forum.imasters.com.br/public/style_emoticons/default/seta.gif Parte 2 - http://www.becck.com/?sec=visualizarMaterias&id_materia=133&id_coluna=25
Bem se não me engano tem que fazer login pra baixar o vídeo, vejam aí.
Opa, Mathias.
Estou novamente aqui para tirar uma dúvida, espero que possa me dar uma luz. Quanto a questão que abordei acima, consegui sim resolver... adaptei apenas pra utilizar com Mysqli e ficou perfeito...
Utilizo a estrutura do banco com as informações do menu para alimentar o "Breadcrumb" das páginas e informar o menu atual e o menu pai.
Ficando assim:
/applications/core/interface/imageproxy/imageproxy.php?img=http://img218.imageshack.us/img218/6540/breadok.jpg&key=94e553808dce68c7ac52fdc9493b6f111b8b868eb9e5ffe10259de97a515c29f" alt="Imagem Postada" />
Fiz uma consulta e confesso que está horrível, ficando assim:
breadcrumb.php
<?php
$link = explode(".php", $_SERVER["PHP_SELF"]);
$link = implode("/", $link);
$re = $mysqli->query("SELECT * FROM `menu`");
while($arrBread = $re->fetch_object()) {
if($link == $arrBread->menuLink) {
$menuPai = $arrBread->menuIdPai;
$menuNome = $arrBread->menuNome;
if($arrBread->menuIdPai == $menuPai) {
$re = $mysqli->query("SELECT * FROM `menu` WHERE id_menu = $menuPai");
$l = $re->fetch_object();
$menuPaiNome = $l->menuNome;
}
echo "\t\t\t\t<h2>".$menuNome."</h2>\r\n";
echo "\t\t\t\t<h3>".$menuPaiNome." » ".$menuNome."</h3>\r\n";
}
}
?>/applications/core/interface/imageproxy/imageproxy.php?img=http://img146.imageshack.us/img146/6206/mysqlr.jpg&key=76eda77770ce3864aa6fbc427588e9f6a991f850fee8b0837ada67e1065d334c" alt="Imagem Postada" />
Existe um modo de melhor fazer isto?
alguma ideia?
Mto bom!..
So gostaria de saber como faco para apos clicar em uma subcategoria
qual seria a function para aparecer apenas menuPai e seus subMenus
Exemplos: Apos clicar em Missao, aparecer apenas
-A Empresa
-Sobre Nós
-Missão da Empresa
-Visão da Empresa
-Objetivos
A ideia eh fazer algo parecido com o americanas.com
Por exemplo: Ao clicar na categoria tvs, aparece os subGrupo > Até 26" - 32 a 37 40 a 47 etc..
Aí vai ser algo com Javascript/CSS. Dá uma procurada por isto. Com o jQuery dá pra fazer alguns efeitos bem legais, de forma simples. Se não conseguir fazer, crie um tópico no fórum de Javascript.
Carlos Eduardo
Olá profissionais do iMasters!
Bom Domingo e feliz natal a todos!
Gostaria de saber como eu faço para usar este menu assim:
pegando o id do menu via get e usar no SELECT do DB, tem como?
TENTEI ASSIM MAS DEU ERRO:
<?php
// pegar o id do menu via get e usar no SELECT do DB, tem como?
$id_do_menu = $_GET['id'];
$mysqli = new mysqli('localhost','root','','imasters');
$query = $mysqli->query('SELECT * FROM menu WHERE = '$id_do_menu' ORDER BY menuIdPai');
while($row = $query->fetch_object())
{
$menuItens[$row->menuIdPai][$row->menuId] = array('link' => $row->menuLink,'name' => $row->menuNome);
}
?>
E TAMBÉM TENTEI ASSIM MAS TAMBÉM DEU ERRO:
$id_do_menu = $_GET['id'];
function imprimeMenuInfinito( array $menuTotal , $idPai = '$id_do_menu' )
{
Alguém poderia me ajudar?
Existe também uma implementação orientada a objetos desse mesmo menu, ela é feita utilizando-se o padrão de projeto Composite:
/applications/core/interface/imageproxy/imageproxy.php?img=http://www.visualcom.com.br/tutoriais/design_pattern/Composite.png&key=be4d381da3ffe15de7ab09252afa7d895e2a46d3bb0121fa427327852f861e46" alt="Imagem Postada" class="bbc_img">
Utilizando o padrão Composite para essa mesma tabela ficaria assim:
while( !is_null( $row = $query->fetch_object() ) )
Seguem as classes:
IComponent.php
/**
* Declara a interface para os objetos na composição
* @category Composite
*/
interface IComponent {
/**
* Desenha a composição
*/
public function draw();
/**
* Define uma referência explícita ao pai
* @param IComponent $super
*/
MenuComposite.php
/**
* Define um objeto da composição
* @category Composite
*/
class MenuComposite implements IComponent {
/**
* @var SplDoublyLinkedList
*/
private $list;
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $action;
/**
* @var IComponent
*/
private $super;
/**
* @var boolean
*/
private $show = true;
/**
* Cria um novo objeto do menu
* @param string $name O nome do menu
* @param string $action A ação que será tomada
*/
/**
* Adiciona um submenu
* @param IComponent $menu
*/
/**
* Desenha o menu
* @param integer $level
*/
/**
* Define uma referência explícita ao pai
* @param IComponent $super
*/
Menu.php
/**
* Define o super menu
* @category Composite
*/
class Menu extends MenuComposite {
/**
* @var SplObjectStorage
*/
private $storage;
/**
* Constroi o super menu
*/
/**
* Insere um submenu na posição correta
* @param IComponent $menu
* @param integer $id O identificador do menu que está sendo adicionado
* @param integer $super O identificador do pai do menu que está sendo adicionado
*/
/**
* Sobrescreve o método setSuper para não permitir que o super menu seja adicionado em outro menu
* @param IComponent $super
*/
/**
* Garante que um ID seja armazenado só e somente só uma vez
* @param IComponent $menu
* @param integer $id O identificador do menu
*/
Links para a documentação das duas classes SPL utilizadas:
SplDoublyLinkedList
SplObjectStorage
;)