Já é a segunda vez que eu preciso fazer isto e só agora que eu achei uma solução realmente mais clean. Não que seja elegante… Parece mais um hack que qualquer outra coisa e nem funciona da maneira que eu realmente gostaria, mas foi o melhor que consegui.
Imagine o seguinte cenário: Você tem um blog e este blog tem um model “Post” e um model “Tag” onde “Post” HABTM “Tag” e vice versa. Agora imagine que você quer encontrar todos os posts com as tags “pessoal”, “vida marinha” e “caçadas”, como você faz? Dizer “$this->Post->findAll(array(’Tag.id’ => 1));” não vai funcionar simplismente porque nesta busca não há nenhuma associação com o model “Tag”, então como fazer?
A solução é você criar um model para a tabela que une os outros dois models [neste caso, a tabela posts_tags] que vai se chamar “PostsTag”. Dentro deste model você não precisa declara nenhuma associação. Simplismente jogue o model lá com as variáveis $name e $userTable e pronto. Quando você for fazer alguma consulta que necessite do filtro via tags, você realiza uma associação hasOne “on the fly” com este novo model. Vou exemplificar:
$this->Post->bindModel(array('hasOne' => array('PostsTag' => array())), false);
O que este comando faz? Ele cria uma associação “hasOne” do model “Post” com o model “PostsTag”. Uma associação hasOne cria um LEFT JOIN no seu query, te permitindo buscar também pelo id de “Tag” também. No CakePHP 1.2, o método bindModel possui uma segunda váriavel chamada $rest que é padrão “true”. Esta variável determina se a associação feita será persistente para outras consultas ou não. Se $reset for “true”, a associação só funcionará para a próxima consulta feita, sendo resetada depois disso. Se “false”, a associação persiste naquele método para o restante das consultas também. Tendo feito isso, você pode fazer uma consulta da seguinte forma:
$condicao['PostsTag.tag_id'] = array(1, 5, 10); //se for um valor apenas, ele não precisa estar num array $this->Post->findAll($condicao); //você busca passando as condições anteriores
Preste atenção que estamos buscando pela “tag_id” em “PostsTag” ao invés de procurarmos pela “id” em “Tag”. Sua query vai ficar mais ou menos assim:
SELECT `Post`.`id`, `Post`.`title` FROM `posts` AS `Post` LEFT JOIN `posts_tags` AS `PostsTag` ON (`PostsTag`.`post_id` = `Post`.`id`) WHERE `PostsTag`.`tag_id` IN (1, 5, 10)
Até aqui, tudo parece ótimo, mas quando você vir os resultados, pode acontecer de aparecerem dois ou mais registros repetidos por causa das múltiplas associações. Para previnir isso, você tem que adicionar o comando DISTINCT ao id de posts, e você pode fazer isso declarando a variável $fields na hora de fazer a consulta, ficando assim:
$condicao['PostsTag.tag_id'] = array(1, 5, 10);
$fields = array('DISTINCT Post.id', 'Post.title');
$this->Post->findAll($condicao, $fields);
Sua query vai ficar mais ou menos assim:
SELECT DISTINCT `Post`.`id`, `Post`.`title` FROM `posts` AS `Post` LEFT JOIN `posts_tags` AS `PostsTag` ON (`PostsTag`.`post_id` = `Post`.`id`) WHERE `PostsTag`.`tag_id` IN (1, 5, 10)
Pronto, você já conseguiu fazer uma consulta filtrando através de uma associação hasAndBelongsToMany. Eu precisei fazer isso numa paginação, então depois eu escrevou outro post sobre como personalizar e definir outros parâmetros na hora de paginar seus resultados baseado no que eu tive que fazer.
Ah! Vale lembra que nada disso fui eu quem “descobri”. Tive que ler diversos emails no grupo do CakePHP e tudo mais. Por isso, qualquer dúvida, procure lá no grupo ou pergunte pra mim que eu vou tentar descobrir. É isso, abraços!
Tags: CakePHP, hasAndBelongsToManyComentários /comments
Este post ainda não possui comentários