Selecionando posts relacionados pelas tags (Code Igniter com Data Mapper)

Desenvolvendo o backend da nova versão do And After resolvi aprimorar o sistema de recomendações de posts relacionados do site e torná-lo mais relevante para os leitores.

Para acompanhar este post é necessário entender sobre relacionamento no banco de dados, saber um pouco como funciona o Data Mapper, a ferramenta ORM e também ter uma noção de relacionamento de tabelas com o Data Mapper.

Posts relacionados – Como funciona hoje?

Atualmente o sistema pega as tags de um post e, uma a uma, busca outros posts que utilizem a mesma tag. Estes posts são exibidos (sem repetição) e com um limite estipulado.

Isso significa que as últimas tags do post que o leitor está lendo podem ser ignoradas – pois possivelmente com as primeiras tags o sistema de relacionamento já tem posts suficientes para uma lista de recomendação.

 

Como deveria funcionar?

Para "afinar" o sistema de recomendação o ideal é levar em consideração todas as tags de dois posts que podem ser similares e ver quantas tags eles tem em comum. Quanto mais tags em comum, mais relacionado está o texto – porém não podemos esquecer de levar em consideração a data de publicação do mesmo, para evitar ficar exibindo apenas posts muito antigos.

 

Tecnologias

Estou utilizando PHP com o framework Code Igniter (tudo bonitinho com MVC) e banco de dados MySQL, a ferramenta ORM utilizada é o Data Mapper (leia também: como instalar e configurar o Data Mapper no Code Igniter).

Para atingir o objetivo criei a seguinte regra de negócio:

  • Percorrer cada tag que o post atual tem e buscar os posts recentes que contém esta mesma tag.
  • Atribuir um "ponto" para cada vez que um post é encontrado
  • Ordenar a lista de posts relacionados pelos "pontos" (número de vezes que um post retornou como relacionado do atual)

 

Vamos ao código! A função abaixo recebe duas variáveis: $post (o id do post que o usuário está lendo) e $limit (quantos posts relacionados devem retornar no array de resultado).

function getRelatedPost($post, $limit) {
  //Pega o post atual e suas tags
  $p = new Post();
  $p->select("id");
  $p->where("id", $post);
  $p->get(1);
  $tags = $p->tag->get();
  $arr = array();
  //Percorre as tags do post atual
  foreach($tags as $t){
    //pega os posts relacionados pela tag
    $related = new Post();
    $related->where_related_tag("id", $t->id);
    $related->where("id <>", $post);
    $related->order_by("creationDate DESC");
    $related->get($limit);
    //Se existirem posts, percorre cada post relacionado para esta tag
    if(!empty($related->id)){
      foreach($related as $pr){
        //Se o post já existir no array soma mais um ponto para ele
        //Se ele não existir cria ele no array com valor 1
        $id = $pr->id;
        if(!empty($arr[$id])){
          $arr[$id] = ($arr[$id]+1);
        }else{
          $arr[$id] = 1;
        }
      }
    }
  }
  //Ordena o array pelos pontos
  //e pega cada post relacionado para retornar como resultado
  arsort($arr, SORT_REGULAR);
  $relArr = array();
  foreach ($arr as $i => $value) {
    $relArr[] = $this->getPostById($i);
  }
  return($relArr);
}
 
 
 
Lembrando que estou utilizando a versão OverZealous do Data Mapper, que foi desenvolvida como um upgrade da versão padrão desta ferramenta.
 
E agora o que rola por trás deste código todo, que o Code Igniter retorna no benchmark para os curiosos de plantão:
 
 
SELECT `tags`.*
FROM (`tags`)
LEFT OUTER JOIN `posts_tags` as posts_tags ON `tags`.`id` = `posts_tags`.`tag_id`
LEFT OUTER JOIN `posts` as posts ON `posts`.`id` = `posts_tags`.`post_id`
WHERE `posts`.`id` = 8 
 
 
SELECT `posts`.*
FROM (`posts`)
LEFT OUTER JOIN `posts_tags` as posts_tags ON `posts`.`id` = `posts_tags`.`post_id`
LEFT OUTER JOIN `tags` as tags ON `tags`.`id` = `posts_tags`.`tag_id`
WHERE `tags`.`id` = 14
AND `posts`.`id` <> 8
ORDER BY `posts`.`creationDate` DESC
LIMIT 5 

 

Qualquer dúvida, correção ou melhoria no código comente! 🙂

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *