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
-
Éber escreveu:Postado em July 10, 2009 às 3:18 pmCara, tem um jeito mais fácil sim!
Eu preciso postar depois! Me lembre!
Esse método já está defasado… -
Lucas Renan escreveu:Postado em July 17, 2009 às 3:08 pmse você puder me mandar o código eu agradeceria muito, já que não encontrei outra solução a não ser dar um bind no model em tempo de execução.
valeu
cara,
eu to invocado com isso, uhauahhua.
foi o único jeito que eu consegui fazer também, porém estou achando muito trabalhoso para um simples LEFT JOIN (no meu caso é INNER JOIN).
Será que não pode acontecer dois acessos simultaneos (em páginas diferentes que utilizem o mesmo model e cada página precisa do tipo diferente de associação) e de repente dar algum conflito?