Objetivo e Funcionalidade
O objetivo é implementar uma funcionalidade semelhante ao sistema de rsepostas em árvore do Baidu Tieba, onde os tópicos são exibidos em estrutura hierárquica sem repetição de mensagens.
Exemplo de Estrutura
A visualização segue um padrão de indentação que representa o nível de profundidade:
* Resposta //Post 1
** Resposta //Resposta filha do Post 1
*** Resposta //Resposta neta do Post 1
**** Resposta //Resposta bisneta do Post 1
***** Resposta //...
****** Resposta
******* Resposta
******** Resposta
********* Resposta
********** Resposta
*********** Resposta
************ Resposta
* Resposta //Post 2
** Resposta //Filho do Post 2
* Resposta //Post 3
** Resposta //...
Estratégia de Implementação
Existem duas abordagens principais:
- Carregamento completo: Buscar todas as respostas em memória e organizar em estrutura de árvore
- Carregametno incremental: Obter apenas respostas de primeiro nível, carregar sub-respsotas via AJAX conforme necessário
Estrutura de Dados no Banco
Tabela MySQL com campos para manter relações hierárquicas:
| Campo | Descrição |
|---|---|
| id | ID único da mensagem |
| root_id | ID da mensagem raiz |
| parent_id | ID da mensagem pai |
| next_sibling_id | ID da próxima mensagem irmã |
| first_child_id | ID da primeira resposta filha |
| last_child_id | ID da última resposta filha |
| nivel | Nível de profundidade |
| tempo_int | Timestamp da postagem |
| tempo_str | Data/hora em formato string |
| conteudo | Conteúdo da mensagem |
Operações de Inserção
Código para adicionar mensagens raiz:
public function criarMensagemRaiz($conteudo = 'Nova Mensagem') {
$dados = [
'root_id' => 0,
'parent_id' => 0,
'next_sibling_id' => 0,
'first_child_id' => 0,
'nivel' => 0,
'conteudo' => $conteudo
];
$timestamp = time();
$data_formatada = date('YmdH', $timestamp);
$dados['tempo_int'] = $timestamp;
$dados['tempo_str'] = $data_formatada;
return $this->conexaoBD->inserir('mensagens', $dados);
}
Código para adicionar respostas:
public function adicionarResposta($mensagem_pai_id, $conteudo = 'Resposta') {
$consulta = "id={$mensagem_pai_id}";
$resultado = $this->conexaoBD->buscarUm($consulta);
$id_atual = $resultado['id'];
$id_raiz = $resultado['root_id'];
$primeiro_filho = $resultado['first_child_id'];
$ultimo_filho = $resultado['last_child_id'];
$nivel_atual = $resultado['nivel'];
$dados_resposta = [
'parent_id' => $mensagem_pai_id,
'next_sibling_id' => 0,
'first_child_id' => 0,
'conteudo' => $conteudo
];
$dados_resposta['root_id'] = $nivel_atual ? $id_raiz : $id_atual;
$timestamp = time();
$data_formatada = date('YmdH', $timestamp);
$dados_resposta['nivel'] = ++$nivel_atual;
$dados_resposta['tempo_int'] = $timestamp;
$dados_resposta['tempo_str'] = $data_formatada;
$novo_id = $this->conexaoBD->inserir('mensagens', $dados_resposta);
if (!$primeiro_filho) {
$condicao = "id = {$id_atual}";
$atualizacao = ['first_child_id' => $novo_id];
$this->conexaoBD->atualizar('mensagens', $atualizacao, $condicao);
}
if ($ultimo_filho) {
$condicao = "id = {$ultimo_filho}";
$atualizacao = ['next_sibling_id' => $novo_id];
$this->conexaoBD->atualizar('mensagens', $atualizacao, $condicao);
}
$condicao = "id = {$id_atual}";
$atualizacao = ['last_child_id' => $novo_id];
$this->conexaoBD->atualizar('mensagens', $atualizacao, $condicao);
return $novo_id;
}
Recuperação e Ordenação de Dados
Método Recursivo
public function obterDetalhesMensagem($id_raiz) {
$this->id_raiz_atual = $id_raiz;
$condicao = 'id = ' . $id_raiz;
$raiz = $this->conexaoBD->buscarUm($condicao);
$primeiro_filho = $raiz['first_child_id'];
$condicao = 'root_id = ' . $id_raiz;
$this->lista_mensagens = $this->conexaoBD->buscarTodos($condicao, '', '', '', 'id');
$this->processarHierarquia($this->lista_mensagens[$primeiro_filho]);
return [
'mensagens' => $this->lista_mensagens,
'ordem' => $this->ordem_processamento
];
}
private function processarHierarquia($no_atual) {
$this->ordem_processamento[$no_atual['id']] = $no_atual['id'];
if ($no_atual['first_child_id'] && empty($this->ordem_processamento[$no_atual['first_child_id']])) {
$this->processarHierarquia($this->lista_mensagens[$no_atual['first_child_id']]);
} elseif ($no_atual['next_sibling_id']) {
$this->processarHierarquia($this->lista_mensagens[$no_atual['next_sibling_id']]);
} elseif ($this->lista_mensagens[$no_atual['parent_id']]['next_sibling_id']) {
$this->processarHierarquia($this->lista_mensagens[$this->lista_mensagens[$no_atual['parent_id']]['next_sibling_id']]);
} elseif ($no_atual['parent_id'] != $this->id_raiz_atual) {
$this->processarHierarquia($this->lista_mensagens[$no_atual['parent_id']]);
}
}
Método Iterativo
public function obterComEstruturaIterativa($id_raiz) {
$this->id_raiz_atual = $id_raiz;
$condicao = 'id = ' . $id_raiz;
$raiz = $this->conexaoBD->buscarUm($condicao);
$primeiro_filho = $raiz['first_child_id'];
$condicao = 'root_id = ' . $id_raiz;
$ordem = 'id';
$this->lista_mensagens = $this->conexaoBD->buscarTodos($condicao, '', $ordem, '', 'id');
$this->processarIterativamente($raiz);
$this->lista_mensagens[$id_raiz] = $raiz;
unset($this->ordem_processamento[0]);
return [
'mensagens' => $this->lista_mensagens,
'raiz' => $raiz,
'ordem' => $this->ordem_processamento
];
}
private function processarIterativamente($mensagem_raiz) {
$this->ordem_processamento[] = $mensagem_raiz['id'];
$this->ordem_processamento[] = $mensagem_raiz['first_child_id'];
$this->ordem_processamento[] = $mensagem_raiz['last_child_id'];
foreach ($this->lista_mensagens as $id_atual => $valor) {
$chave_atual = array_search($id_atual, $this->ordem_processamento);
$primeiro_filho = $valor['first_child_id'];
$ultimo_filho = $valor['last_child_id'];
$irmao_seguinte = $valor['next_sibling_id'];
if ($primeiro_filho && ($primeiro_filho != $this->ordem_processamento[$chave_atual + 1])) {
array_splice($this->ordem_processamento, $chave_atual + 1, 0, $primeiro_filho);
if ($ultimo_filho && ($ultimo_filho != $primeiro_filho)) {
array_splice($this->ordem_processamento, $chave_atual + 2, 0, $ultimo_filho);
}
}
if ($irmao_seguinte) {
$chave_irmao = array_search($irmao_seguinte, $this->ordem_processamento);
if (!$chave_irmao) {
if ($ultimo_filho) {
$chave_ultimo = array_search($ultimo_filho, $this->ordem_processamento);
array_splice($this->ordem_processamento, $chave_ultimo + 1, 0, $irmao_seguinte);
} elseif ($primeiro_filho) {
array_splice($this->ordem_processamento, $chave_atual + 2, 0, $irmao_seguinte);
} else {
array_splice($this->ordem_processamento, $chave_atual + 1, 0, $irmao_seguinte);
}
}
}
}
}
Renderização HTML
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<?php
echo $raiz['conteudo'], ' >>> Autor '.$raiz['id'].' Data: ', $raiz['tempo_str'], '<hr>';
foreach ($ordem as $valor) {
for($contador = 0; $contador < $mensagens[$valor]['nivel']; ++$contador) {
echo '|-';
}
$id_atual = $mensagens[$valor]['id'];
$id_raiz_atual = $mensagens[$valor]['root_id'];
echo $id_atual.'diz: @'. $mensagens[$mensagens[$valor]['parent_id']]['id']. ' ' .$mensagens[$valor]['conteudo'].' ['.$mensagens[$valor]['tempo_str']."] <a href='{$url_controle}/responder?id={$id_atual}&rootid={$id_raiz_atual}'><Responder></a><br>";
}
?>
</body>
</html>
Abordagem Prática
Na maioria dos casos, as respostas têm apenas um nível de profundidade. Uma abordagem mais eficiente seria:
- Carregar apenas as respostas de primeiro nível no servidor
- Usar AJAX para buscar respostas aninhadas dinamicamente
Método para obter respostas de primeiro nível:
public function buscarRespostasNivel1($id_raiz) {
$condicao = "root_id = {$id_raiz} AND nivel = 1";
return $this->conexaoBD->buscarTodos($condicao);
}
Essa abordagem oferece melhor desempenho para casos reais, evita sobrecarga de dados desnecessários e melhora a experiência do usuário com carregamento progressivo.