Categorias e subcategorias: Exemplo de modelagem
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