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! 🙂

Usando o Data Mapper no Code Igniter

Este post nasceu da necessidade de explicar um pouco mais o funcionamento do Data Mapper para o framework Code Igniter, para ficar mais fácil fazers posts mais avançados utilizando esta ótima ferramenta.

O que é Object-relational mapping (ORM)?

ORM é uma técnica que facilita o acesso e manipulação de banco de dados "transformando-os" como se fossem objetos.

Utilizando ORM o desenvolvedor não precisa se preocupar em escrever a SQL diretamente, a ferramenta ORM (no meu caso o Data Mapper) representa as tabelas do banco de dados com classes, e instâncias destas classes fazem as manipulações e consultas necessárias.

As principais vantagens do ORM é a produtividade, pois o código escrito reduz bastante, facilitando desenvolver e manter aplicações.

Eu comecei recentemente a utilizar o Code Igniter (o primeiro projeto é o novo And After) e estou gostando muito, estou muito mais produtivo mesmo trabalhando com linguagem e ferramentas que ainda não domino completamente: PHP, Code Igniter e ORM.

 

Database

Database,database… qual era aquela coluna mesmo?

Data Mapper

Data Mapper (DM) é um ORM para quem utiliza o framework Code Igniter (CI), e facilita muito qualquer consulta e manipulação ao banco.

O primeiro passo é instalar o Data Mapper no seu CI, seguindo os passos abaixo:

  1. Baixe a última versão do DM e descompacte
  2. Opcional: faça suas configurações do DM no arquivo application/config/datamapper.php
  3. Coloque o  application/config/datamapper.php na pasta application/config folder do seu CodeIgniter
  4. Coloque o application/libraries/datamapper.php na pasta application/libraries do seu Code Igniter.
  5. Coloque o application/languages/english/datamapper_lang.php na pasta application/language/english do seu Code Igniter (aqui você pode adicionar os idiomas que quiser, eles contem as mensagens de erro do DM).
  6. Configue o  application/config/autoload.php e adicione a biblioteca do datamapper e database no array do autoload

$autoload["libraries"] = array("database", "datamapper");

Com isso seu aplicativo deve estar pronto para utilizar o Data Mapper!

Abaixo vou publicar exemplos básicos de uso do Data Mapper para consultar o seu banco alterar e inserir dados. Será bem superficial, o objetivo deste post é introduzir  ORM e o Data Mapper para quem está começando ou pretende começar a utilizar o Code Igniter.

 

Começando – uma classe para cada tabela

Relembrando: utilizando uma ferramenta ORM (neste caso o Data Mapper) cada tabela será representada por uma classe no seu Model (usando MVC), e esta classe extenderá a classe do Data Mapper.

Como exemplo vou usar parte do código do backend desenvolvido para o And After, temos uma tabela users, então na camada Model temos a seguinte classe:

class User extends DataMapper {
    var $table = "users";

    function User()
    {
        parent::DataMapper();
    }
}

 

Neste exemplo acima a definição da variável $table é opcional, pois quando ela não está definida o Data Mapper automaticamente define table como o plural (adicionando o "s" ao final) do nome da sua classe. 

 

Consultando o banco de dados – Função Get

Agora vamos ver como o Data Mapper aumenta a produtividade no desenvolvimento, no exemplo abaixo vou fazer uma consulta que retorne todas as linhas e colunas da tabela users, utilizando a classe User criada no exemplo acima.

$u = new User();
$u->get();

Fácil assim, o $u é um objeto com todos os resultados da nossa consulta que seria o mesmo que:

get * from users

 

Limit

Sabemos que não é bom para a performance da nossa aplicação o get * e nem consultas sem um LIMIT definido, para definir o limite é simples, vamos retornar 5 usuários apenas:

$u = new User();
$u->get(5);

E no exemplo abaixo, pegando 5 usuários começando a partir do 10:

$u = new User();
$u->get(5,10);

 

Selecionando as colunas – select

E agora selecionando as colunas que serão retornadas para evitar consultar dados desnecessários, vamos pegar apenas o user e email dos usuários.

$u = new User();
$u->select("user,email");
$u->get(5);

 

Fazendo buscas – where

Vamos buscar o usuário que tenha o user "tester":

$u = new User();
$u->where("user", "tester");
$u->get(1);

 

Você pode usar diversos "where" em uma mesma consulta, e existe também o "or_where" como no exemplo abaixo:

Fazendo buscas – or_where

$u = new User();
$u->where("user", "tester");
$u->or_where("id", "1");
$u->get();

Neste caso vai retornar 1 user se o user com ID 1 for o "tester" ou dois, caso eles sejam diferentes.

 

Ordenando a consulta – order_by

Pegando 15 usuários ordenados pelo nome:

$u = new User();
$u->order_by("name asc")
$u->get(15);

 

Aqui estão os exemplos básicos de consultas utilizando o Data Mapper, recomendo que você leia e releia a documentação da função Get (en) para consultas avançadas.

 

Manipulando os objetos da consulta

Agora você aprendeu a fazer a consulta, já tem sua camada de Model estruturada com uma classe para cada tabela, vamos manipular os dados!

Para exemplificar como é a manipulação dos dados de uma consulta, vamos criar um array com o nome dos últimos usuários cadastrados:

//Consulta os usuários ordenado pela data
$u = new User();
$u->select("name");
$u->order_by("date DESC");
$u->get(10);

//Vamos criar um array com o nome dos usuários da consulta acima
$arr = array();
foreach ($u->all as $user)
{
    $arr[] = $user->name;
}

 

Quando o resultado é apenas um objeto pode ser feito da seguinte forma:

$u = new User();
$u->select("id,name");

$u->get(1);

echo $u->id ." - ". $u->name;

 

Espero que você, que usa o Code Igniter mas ainda não usa o Data Mapper em breve seja mais um adepto!

O próximo post sobre o Code Igniter será o relato da dificuldade que tive para estruturar a primeira aplicação e dividir corretamente as camadas Model, View e Controller (MVC) e do Data Mapper já tenho um começado que trata de relacionamento de tabelas do banco de dados utilizando esta ferramenta.

Espero que gostem, dúvidas, críticas e sugestões nos comentários!