Ir para o conteúdo

Publicidade

 Estatísticas do Fórum

  • 0 Usuários ativos

    0 membro(s), 0 visitante(s) e 0 membros anônimo(s)

Foto:

Menu em PHP com MySQL

  • Por favor, faça o login para responder
11 respostas neste tópico

#1 Matias Rezende

Matias Rezende

    Consultor Web

  • Moderadores Globais
  • 6.225 posts

Postado 17 janeiro 2010 - 16:38

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:
  • Faz-se uma consulta de todos os registros.
  • Dentro do laço que exibe os dados, pega-se o idPai e consulta-se todos os registros que tenham este mesmo id
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

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
                )
        )
)
Onde:
  • o primeiro índice do array é o menuIdPai
  • o segundo índice do array é o menuId
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:
  • A Empresa
    • Sobre Nós
      • Missão da Empresa
      • Visão da Empresa
    • Objetivos
  • Contato
  • Produtos
    • Televisão
    • Informática
    • Computadores
    • Monitores
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;
}
// 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.

Carlos Eduardo - Blog OGordo
  • 0

#2 João Batista Neto

João Batista Neto

    Verschränkung

  • Administradores
  • 4.547 posts

Postado 18 janeiro 2010 - 07:11

Existe também uma implementação orientada a objetos desse mesmo menu, ela é feita utilizando-se o padrão de projeto Composite:

Imagem postada

Utilizando o padrão Composite para essa mesma tabela ficaria assim:


$mysqli = new mysqli( '127.0.0.1' , 'usuario' , 'senha' , 'banco_de_dados' );
$query = $mysqli->query('SELECT * FROM menu ORDER BY menuIdPai');
$menu = new Menu();

while( !is_null( $row = $query->fetch_object() ) )
$menu->push( new MenuComposite( $row->menuNome ) , $row->menuId , $row->menuIdPai );

$menu->draw();


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
*/
public function setSuper( 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
*/
public function __construct( $name , $action = '#' , $show = true ){
$this->list = new SplDoublyLinkedList();

if ( ( $this->show = (bool) $show ) !== false ){
$this->name =& $name;
$this->action =& $action;
}
}

/**
* Adiciona um submenu
* @param IComponent $menu
*/
public function add( IComponent $menu ){
$menu->setSuper( $this );
$this->list->push( $menu );
}

/**
* Desenha o menu
* @param integer $level
*/
public function draw(){
if ( $this->show ){
echo '<li>';
printf( '<a href="%s">%s</a>' , $this->action , $this->name );
}

if ( $this->list->count() ){
echo '<ul>';
foreach ( $this->list as $item ) $item->draw();
echo '</ul>';
}

if ( $this->show ) echo '</li>';
}

/**
* Define uma referência explícita ao pai
* @param IComponent $super
*/
public function setSuper( IComponent $super ){
$this->super =& $super;
}
}


Menu.php

/**
* Define o super menu
* @category Composite
*/
class Menu extends MenuComposite {
/**
* @var SplObjectStorage
*/
private $storage;

/**
* Constroi o super menu
*/
public function __construct(){
parent::__construct( null , null , false );

$this->storage = new SplObjectStorage();
$this->storage->attach( $this , null );
}

/**
* 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
*/
public function push( IComponent $menu , $id , $super = null ){
foreach ( $this->storage as $item ){
if ( $this->storage[ $item ] == $super ){
$item->add( $menu );
$this->safeStore( $menu , $id );
break;
}
}
}

/**
* Sobrescreve o método setSuper para não permitir que o super menu seja adicionado em outro menu
* @param IComponent $super
*/
public function setSuper( IComponent $super ){
throw new LogicException( 'O Super menu não pode ser tratado como submenu ou item de menu' );
}

/**
* Garante que um ID seja armazenado só e somente só uma vez
* @param IComponent $menu
* @param integer $id O identificador do menu
*/
private function safeStore( IComponent &$menu , $id ){
$add = true;

foreach ( $this->storage as $item ) if ( $this->storage[ $item ] == $id ){
$add = false;
break;
}

if ( $add ) $this->storage->attach( $menu , $id );
}
}


Links para a documentação das duas classes SPL utilizadas:
SplDoublyLinkedList
SplObjectStorage

;)


  • 0

#3 Alaerte Gabriel

Alaerte Gabriel

    Aplicações Especiais PHP

  • Membros
  • 5.813 posts

Postado 18 janeiro 2010 - 10:39

Eu gosto dela... utilizo a class...
  • 0

#4 Rasp

Rasp

    the write less, do more!

  • Membros
  • 354 posts

Postado 01 maio 2010 - 09:35

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
  • 0

#5 Matias Rezende

Matias Rezende

    Consultor Web

  • Moderadores Globais
  • 6.225 posts

Postado 01 maio 2010 - 16:02

Qual dos dois está utilizando? A classe do João Batista ou a função que eu fiz?

Carlos Eduardo
  • 0

#6 Rasp

Rasp

    the write less, do more!

  • Membros
  • 354 posts

Postado 01 maio 2010 - 17:45

A classe que você fez mesmo.

vlw!
  • 0

#7 Matias Rezende

Matias Rezende

    Consultor Web

  • Moderadores Globais
  • 6.225 posts

Postado 01 maio 2010 - 17:55

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
  • 0

#8 Alaerte Gabriel

Alaerte Gabriel

    Aplicações Especiais PHP

  • Membros
  • 5.813 posts

Postado 03 maio 2010 - 09:30

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:

:seta: Parte 1 - http://www.becck.com...32&id_coluna=25
:seta: Parte 2 - http://www.becck.com...33&id_coluna=25

Bem se não me engano tem que fazer login pra baixar o vídeo, vejam aí.
  • 0

#9 Rasp

Rasp

    the write less, do more!

  • Membros
  • 354 posts

Postado 11 outubro 2010 - 14:37

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:
Imagem postada
Clique aqui


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." &raquo; ".$menuNome."</h3>\r\n";
    }
}
?>

Estrutura Mysql:
Imagem postada
Clique aqui


Existe um modo de melhor fazer isto?

alguma ideia?
  • 0

#10 ro_wagner

ro_wagner

    aprendendo..

  • Membros
  • 121 posts

Postado 10 dezembro 2010 - 11:11

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..
  • 0

#11 Matias Rezende

Matias Rezende

    Consultor Web

  • Moderadores Globais
  • 6.225 posts

Postado 11 dezembro 2010 - 06:46

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
  • 0

#12 Sandro Mattel

Sandro Mattel
  • Membros
  • 8 posts

Postado 22 dezembro 2013 - 14:54

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?

 


  • 0