Um erro muito comum em modelagem de dados concerne a sistemas de categorias e subcategorias. Muitos não sabem como modelar o banco de dados, criam diversas tabelas e acabam complicando o que é simples.
Mostrarei um forma muito simples de como armazenar essas informações num banco de dados e como exibi-las na tela, na forma de lista, técnica muito utilizada para construção de menus.
Vamos à modelagem, primeiramente.
Teremos apenas uma tabela. Esta é a estrutura dela:
(usarei MySQL neste artigo, mas a lógica da modelagem independe do SGBD usado)
CREATE TABLE categorias( id INT(5) UNSIGNED NOT NULL AUTO_INCREMENT, id_pai INT(5) UNSIGNED NOT NULL, nome VARCHAR(20) NOT NULL, PRIMARY KEY (id) ) DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Vamos popular a tabela da seguinte forma:
INSERT INTO categorias(id, id_pai, nome) VALUES (1, 0, 'A Empresa'), (2, 1, 'Sobre Nós'), (3, 1, 'Objetivos'), (4, 3, 'Objetivo dos nossos clientes'), (5, 0, 'Contato'), (6, 0, 'Produtos');
Obteremos a seguinte tabela:
mysql> select * from categorias; +----+--------+----------------------+ | id | id_pai | nome | +----+--------+----------------------+ | 1 | 0 | A Empresa | | 2 | 1 | Sobre Nós | | 3 | 1 | Objetivos | | 4 | 3 | Objetivo dos nossos | | 5 | 0 | Contato | | 6 | 0 | Produtos | +----+--------+----------------------+ 6 rows in set (0,00 sec)
Nessa estrutura, temos três seções principais: “A Empresa”, “Contato” e “Produtos”. As categorias “Sobre Nós” e “Objetivos” são subcategorias de “A Empresa”. E “Objetivos dos nossos clientes” é subcategoria de “Objetivos”, que, por sua vez, é subcategoria de “A Empresa”, como citado anteriormente.
Vamos fazer uma seleção dessas informações e colocá-las num arrray, como o exibido abaixo.
// array que conterá as categorias $cats = array(); $mysqli = new mysqli( 'localhost', 'user', 'senha', 'bancoDeDados' ); $sql = 'SELECT id, id_pai, nome FROM categorias'; $exec = $mysqli->query( $sql ) or exit( $mysqli->error ); $i = 1; while ( $f = $exec->fetch_object() ) { $cats[$i]['id'] = $f->id; $cats[$i]['id_pai'] = $f->id_pai; $cats[$i]['nome'] = $f->nome; $i++; }
Obteremos um array como o exibido abaixo:
$cats[1]['id_pai'] = 0; $cats[1]['nome'] = 'A Empresa'; $cats[2]['id_pai'] = 1; $cats[2]['nome'] = 'Sobre Nós'; $cats[3]['id_pai'] = 1; $cats[3]['nome'] = 'Objetivo'; $cats[4]['id_pai'] = 3; $cats[4]['nome'] = 'Objetivos dos Nossos Clientes'; $cats[5]['id_pai'] = 0; $cats[5]['nome'] = 'Contato'; $cats[6]['id_pai'] = 0; $cats[6]['nome'] = 'Produtos';
Note que o array não começou em zero. Os índices do array são os ID’s das categorias no banco de dados. Se começasse em zero, causaria conflito com o id_pai, que é zero para categorias principais.
Agora postarei uma função simples em PHP que montará o menu completo.
/** * Função que monta o menu com as categorias e subcategorias. * @param id_pai ID da categoria pai cujas subcategorias serão buscadas. * @param ArrayCats Array com as categorias do menu. */ function montaMenu( $id_pai, $arrayCats ) { // calcula o número de índices do array $catsSize = count( $arrayCats ); echo "<ul>"; for ( $i = 1; $i <= $catsSize; $i++ ) { if ( $arrayCats[ $i ]['id_pai'] == $id_pai ) { echo "<li>"; echo $arrayCats[ $i ]['nome']; // busca as subcategorias da categoria atual montaMenu( $arrayCats[ $i ]['id'], $arrayCats ); echo "</li>"; } } echo "</ul>"; }
Você pode usar duas variáveis globais, se quiser: o array das categorias e a variável $catsSize. Isso reduz o processamento, uma vez que a função count() seria chamada apenas uma vez. Porém, para projetos grandes, com diversos menus, essa prática não seria bem-vinda.
Para exibir o menu, basta chamar a função da seguinte forma:
montaMenu( 0, $cats );
Ela começará buscando as categorias principais (id_pai = 0) e depois buscará por cada subcategoria.
Espero que o post tenha clareado a ideia de todos. Qualquer dúvida, basta deixar um comentário.
Abraços







Cara, parabens muito interessante mesmo, mas tipo, tem como vc colocar um link pra download do script já pronto, ou entao separar o tutorial por arquivos separados, tipo quem é a index.php sei lá, pq eu sou novato em php, e calhou de eu precisar de criar um GC com categorias e subcategorias até nivel 5, e vi q essa aí é a melhor solução.
@rodrigo
Olá, Rodrigo. O post não tem a intenção de criar um sistema, por isso não há arquivos. O propósito é mostrar a modelagem apenas. Logo, isso pode estar em qualquer página, seja uma index.php ou uma qualquer_coisa.php.
Como você pode ver, só há duas partes em PHP: a função que gera o menu e o trecho responsável por fazer a seleção. Você pode deixar a função num arquivo somente para ela e dar um include/require nas páginas que usarão a função.
Abraço,
Beraldo
Sim eu sei, mas digo, como eu disse sou pouco experiente em php, teria como vc criar pra eu entender melhor isso em arquivos.php ???
@rodrigo
Eu não gosto de colocar script pronto para evitar que usuários apenas copiem e colem, sem entender o código. Tente fazer e poste suas dúvidas, aqui ou em algum fórum, onde há mais pessoas para ajudá-lo. ;)
Abraço,
Beraldo
ok, aqui deu erro na linha 6, justamente onde está o “mysqli”
é assim mesmo? não era pra ser mysql connect?
@rodrigo
Verifique se a extensão MySQLi está habilitada. Ela está disponível somente para PHP 5
Amigão isso é seguro? pq comigo começou a dar alguns bugs, vc não pode criar um tutorial para criar um portal de notícias com este fundamento?
@Rodrigo
Sim. É seguro. É a melhor modelagem para o caso.
Beraldo,
primeiramente parabens cara vc me deu luz, ou melhor um sol, pois iluminou muito minha mente, tava com um problema nesse negocio de categoria e sub, sempre usei a tabela da forma que demonstrou mas meu problema era ordenar de forma dinamica, mas como mostrou, a função chamando a propria função foi fantastico, tão obvio e não percebi, mas queria deixa uma sugestão de melhoria, vou até postar em meu blog com os devidos créditos a inspiração inicial, seguinte da forma que demonstrou os registros tem que estar ordenados no banco, para que funcione corretamente, mas eu pensei num forma de que não seja necessário que estejam ordenados no banco segue abaixo, o código espero que goste da contribuição.
class Categorias
{
private $org_cat_rows;
function AgrupaCategorias($arrayCats, $pai_id, $pai_campo, $pk_campo)
{
$catsSize = count($arrayCats);
for($i=0; $iorg_cat_rows, $arrayCats[$i]);
self::AgrupaCategorias($arrayCats, $arrayCats[$i][$pk_campo], $pai_campo, $pk_campo);
}
}
}
}
$categorias = new Categorias();
$categoriasAgrupadas = $categorias->AgrupaCategorias($Vetor_com_categorias, 0, ‘campo_pai’, ‘campo_primary_key’);
dessa forma fica tudo OK!!!
Abraços,
John Marques
@John Marques
Olá.
De fato, já haviam me informado sobre isso. Eu esqueci de editar o post.
Valeu por avisar. Já farei a modificação
Opa co
corrigir o Metodo faltou o array_push para inserir no array e metodo que retorna o membro
entao ta ai a classe corrigida
class Categorias
{
private $org_cat_rows;
public function getCategorias()
{
return $this->org_cat_rows;
}
function AgrupaCategorias($arrayCats, $pai_id, $pai_campo, $pk_campo)
{
$catsSize = count($arrayCats);
for($i=0; $iorg_cat_rows, $arrayCats[$i]);
self::AgrupaCategorias($arrayCats, $arrayCats[$i][$pk_campo], $pai_campo, $pk_campo);
}
}
}
}
Olá Beraldo, como vai?
Primeiramente parabéns pela inciativa…
Eu tentei fazer o que você coloco ai…ele não da nenhum erro, mas também não mostra nada..rs
Fica minha pagina toda em branco…você tem alguma ideia do que pode estar acontecendo?
Achei que poderia ser algom com o mysqli mas vi aqui que esta habilitado ja…
;extension=php_mssql.dll
extension=php_mysql.dll
extension=php_mysqli.dll
;extension=php_oci8.dll
Preciso muito disso para um projeto..que vai ser um catalogo com categorias de produtos e subcategorias.
Desde ja agradeço
@guilherme
Verifique se você está com as mensagens de erro habilitadas.
Para habilitá-las, coloque isto no topo do script:
ini_set( ‘display_errors’, 1 );
error_reporting( E_ALL );
Olá Beraldo,
fiz o que você pediu e continua tudo branco..rs
Tem algum exemplo de como popular um menu list utilizando algo parecido com esse código sem utilizar estrutura de dados??? Obrigado!
@Vinicius
Não entendi… como irá montar um menu sem ter os dados armazenados em algum lugar? De onde os dados virão?
Os dadoes estão no banco, essa rotina que você criou é uma rotina de exibição de menu de categorias, por exemplo, no caso que eu falo de popular um menu list, eu falo de uma rotina para cadastrar essas categorias, por exemplo, um campo onde a pessoa informa o nome da categoria, e um menu list onde ele escolhe uma categoria pai, caso essa seja uma sub categoria, ou uma sub da sub, o que eu quiz dizer é um menu list que mostra as categorias sem que seja com marcação, por exemplo:
isso dentro de um meu list
Categoria 1
Categoria 2
Categoria 2 -> Sub Categoria 1
Categoria 2 -> Sub Categoria 2 -> Sub Categoria 1
Categoria 3
categoria 4
e assim por diante. Obrigado!
Fiz uns testes aqui e deu certo, utilizando recursividade mesmo, se alguém precisar dá um toque que eu escrevo aqui, obrigado!
parabéns, excelente post.
Ola Beraldo!
Existe alguma maneira de usar div ao inves de ul ?
Pergunto pois ja tentei de diversas formas, e ele nao fecha o laco no final
Estou tentando montar uma listagem de produtos, com seu artigo, ficou bem mais pratico mesmo!
Valew, Obrigado!
Olá, Carlos Braga.
É possível usar div, sim. Basta prestar atenção à estilização CSS.
Como seria um formulário para cadastrar as categorias e sub?
Explique melhor sua dúvida.
Basicamente, basta um formulário com o nome da categoria e um
eu postei ela no imasters, só que não tive resposta ainda, se puder me orientar por lá, ou por aqui mesmo, fico agradecido, parabens pela a modelagem.
AQUI
parece que o link não funcionou, se não, é este aqui:
http://forum.imasters.com.br/topic/439254-cadastrar-sub-categorias/
Só tem um problema: Isso é bom apenas para menus. Para evitar a redundância de dados tem que criar mais 2 tabelas. Tabelas: categorias(idcategoria,categoria)
subcategorias(idsubcategoria,subcategoria)
categorias_has_subcategorias(id,categorias_idcategoria,subcategorias_idsubcategoria)
No meu caso tenho que usar assim para evitar redundância de dados.
Não acho que precise de mais tabelas. Uma tabela é suficiente para criar inúmeras subcategorias.
Não entendi onde há redundância na minha modelagem. Pode me dar um exemplo? Talvez eu não tenha percebido essa falha.
Cara, bem legal essa forma de montar categorias, agora eu queria saber o seguinte. Como eu faço um SELECT retornar o nome da categoria como um campo adicional referenciado pelo id_pai. Como se fosse num INNER JOIN, pois os 2 estão na mesma tabela, fiquei meio perdido, será que pode me ajudar?
funcionaria somente com 1 instrução dentro da outra?