Escrevo este posts emocionado, com uma ligeira lágrima escorrendo no rosto rsrs… Finalmente estou disponibilizando a versão do meu plugin Filter Results para CakePHP 2 já com o operador BETWEEN e tudo mais. =D

Filter Results

Basicamente, com o Filter Results para CakePHP 2 você poderá fazer formulários de pesquisa da maneira que você desejar, de forma rápida e padronizada para toda a sua aplicação.

Compatibilidade

IMPORTANTE

Na versão 2.3 do plugin houve uma mudança na arquitetura das funções e nome de arquivos. Se estiver encontrando este problema, baixe a versão 2.0 e continue lendo este artigo ou utilize o tutorial da versão 2.3 clicando aqui.

Instalação

Download

Antes de iniciar, baixe o Filter Results. Copie os arquivos para a pasta /app/Plugin/filter_results ou em outro diretório para plugins do CakePHP.

Se preferir, utilize o git:
git clone -b 2.0 https://github.com/pedroelsner/filter_results.git FilterResults

Ativação

Ative o plugin adicionando ao arquivo /app/Config/bootstrap.php:

Configurações

Vamos adicionar nas variáveis $components e $helpers no AppController.php:

Parâmetros de configuração:

  • auto->paginate Se você definir como TRUE, o Paginate será configurado automaticamente.
  • auto->explode Se você definir como TRUE, o valor de pesquisa será quebrado pelo explode->character e concatenado pela condição explode->concatenate.

Ajuste no DebugKit

Se você estiver utilizando o DebugKit, configure-o desta forma em seu AppController.php:

'DebugKit.Toolbar' => array('panels' => array('history' => false));

Antes de tudo

Para este e os próximos exemplos, vamos ter como base o seguinte banco de dados:

Beleza? Então vamos começar!

Filtro Simples

Bom, após gerar as telas pelo Bake, criamos a seguinte action em nosso Controller:

Arquivo /app/Controller/UsersController.php

Vamos então colocar um filtro sobre a grid(tabela) para pesquisar um usuário pelo nome(name) e configurar o Paginate da action.

Arquivo /app/Controller/UsersController.php

A configuração aqui é bem simples: Criamos um filtro chamado filter1 que utilizará o campo User.name. Este filtro utiliza o operador LIKE e adiciona % antes e depois do conteudo a ser filtrado.

Agora temos apenas que fazer o formulário na View em cima da tabela.

Arquivo /app/View/Users/index.ctp

Pronto! Temos um campo que filtra o usuário pelo nome e compatível com o Paginate. ^^

E mais, o Filter Results automaticamente divide o valor de pesquisa para obter um melhor resultado. Por exemplo: se realizarmos um filtro por ‘Pedro Elsner’, a condição será: WHERE ((User.name LIKE '%Pedro%') AND (User.name LIKE '%Elsner%')).

Explode

A opção explode para os operadores LIKE e NOT LIKE estão ativadas por padrão nas configurações do Filter Results. Mas, como você sabe, você pode desativar na declaração dos components no controller. Se você fizer isto, você pode ativar a função explode para um filtro determinado:

Também é possível mudar as opções de quebra para cada um.

Além disso, você também pode usar a função de quebra junto com outro operador (como =). Veja:

Filtro Simples + Regra Composta

Vamos agora fazer mais uma regra dentro do filtro filter1. Queremos que ele filtre pelo nome(name) ou pelo nome do usuário(username).

Alteramos então apenas o nosso Controller:

Arquivo /app/Controller/UsersController.php

A regra OR pode ser também AND ou NOT.

NOTA: Se você definir mais de uma condição sem especificar a regra, o plugin entenderá como AND automaticamente.

Filtro Simples + Regra Fixa

Vamos supor agora que o nosso filtro filter1 quando informado deva filtrar pelo nome(name) E somente usuários(username) ativos.

Arquivo /app/Controller/UsersController.php

Agregação de Filtros

Automaticamente o Filter Results concatena todos os filtros pela regra AND. Usando o exemplo a seguir, informando ‘Pedro’ no filter1 e ‘elsner’ no filter2 teremos a condição: WHERE (User.name LIKE '%Pedro%') AND (User.usernname LIKE '%elsner%')

NOTA: Podemos concatenar os filtros também pelas regras OR ou NOT.

Vamos alterar nosso exemplo para concatenar os filtros pela regra OR, e, se o filtro1 for informado queremos apenas usuários ativos. Desta vamos obter a condição: WHERE ((User.name LIKE '%Pedro%') AND (User.active = 1)) OR (User.usernname LIKE '%elsner%')

Filtro de Seleção

Vamos mudar nosso filtro. Além de filtrar pelo nome, queremos agora filtrar também pelo grupo do usuário(Group.name) desejado através de um campo de seleção.

Arquivo /app/Controller/UsersController.php

Arquivo /app/View/Users/index.ctp

Pronto! Use e abuse de quantos filtros desejar.

Para deixar bem claro, veja a imagem abaixo. Nela temos a View de Produtos, onde é possível filtrar por: Cor, Dimensão, Gramatura, Material e Nome.

Filtro Avançado

Em alguns casos queremos algo diferente diferente, por exemplo, desejamos que o usuário possa escolher o campo e o operador para realizar o filtro.

Arquivo /app/Controller/UsersController.php

NOTA: Perceba que desta vez criamos o filtro filter1 sem nenhum parâmetro. Isto porque as regras serão selecionadas na View.

Arquivo /app/View/Users/index.ctp

Agora, primeiro você pode selecionar o campo (automaticamente o Filter Results lista os campos da tabela), depois o operador e informar o valor desejado para o filtro.

Haverá situações em que você precisará personalizar os operadores para seleção. Por exemplo, vamos deixar somente os campos User.id, User.name e User.username para seleção, e os operadores LIKE e =.

Para isso, mudamos somente a View:

Arquivo /app/View/Users/index.ctp

Operadores

O Filter Resultes possuí operadores pré-definidos, abaixo você encontra todas as opções disponíveis para você utilizar em seus filtros personalizados.

BETWEEN

Também é possível utilizar o operador BETWEEN para consulta entre valores numéricos ou de data. Configure o campo de filtro desta forma:

Para campos de data, adicione a opção 'date' => true para converte a data informada para o formato YYYY-MM-DD:

Relacionamentos HABTM

Por padrão, nos relacionamentos HABTM o CakePHP realiza várias consultas a parte e depois faz um merge nos resultados em um único array. Você já deve ter percebido isso, mas se não, verifique a janela de debug enquanto você faz find() em uma relação HABTM.

Para filtramos nessas relações, precisamos criar alguns “hacks” para que o CakePHP gere um único select, assim o plugin filtra o resultado com comandos where simples.

Todas as informações e explicações necessárias para criar os “hacks” você encontra neste tutorial: Pesquisando em relações HABTM no CakePHP

Agradecimentos

Deixo aqui registrado meu muito obrigado para todos os colegas que de alguma forma contribuíram para o desenvolvimento deste plugin.

Em especial para:

Quer copiar esse post no seu site? Você pode!

Segundo a licença da Creative Commons 3.0 (CC BY SA 3.0) você pode copiar e distribuir esse conteúdo desde que faça menção ao autor original, para isso é só copiar esse código no final do artigo quando for publicá-lo em seu site:

<p>Artigo originalmente publicado em <em>24 de junho de 2012</em> por <strong><a href="http://pedroelsner.com/" title="Pedro Elsner, Profissional de TI - São Paulo">Pedro Elsner</a></strong>: <a href="http://pedroelsner.com/2012/06/plugin-filter-results-para-cakephp-2-x/" title="Utilizando Filter Results 2.0 no CakePHP 2.0">Utilizando Filter Results 2.0 no CakePHP 2.0</a></p>
A não menção ao autor original da obra implicará em cópia e/ou distribuição ilegal de propriedade intelectual, o que é crime segundo a Lei n.º 9.610.
  • Parabéns Elsner! Excelente trabalho e contribuição. Tenha a certeza que sua emoção está contagiando muitos agora. Grande abraço!

  • Valeu Eduardo! =)
    Abraço!

  • Edgar

    Estou começando agora no Cake e os seus tutoriais estão me ajudando muito, espero que venham varios ainda, obrigado

  • Claudio Bonfim

    Olá. Muito bom!
    Como você fez para deixar os campos selects de forma inline, ou seja, um ao lado do outro?
    Valeu mesmo!

  • Você deve estar falando do printscreen de exemplo.
    Usei “float: left;” basicão via CSS.

  • Diogo

    Opa!
    Obrigado pelo pronto atendimento!!!

    Tenho outra dúvida que talvez ajude a aprimorar meus conhecimentos ou o plugin, dependendo da sua análise né…

    Tenho um campo no BD chamado “fluxo” que é um tinyint, como se fosse um boolean (0 ou 1), que representa 0 => “saída” e 1 => “entrada”.

    Ao adicionar o filtro na minha view, ele automaticamente cria um campo do tipo checkbox.
    Nesse caso especifico, com o checkbox eu perco uma forma de filtro, pois sem marcar ele retorna todos os valores e marcando apenas as “entradas”. Nesse caso não consigo filtrar somente as “saídas”.
    O que fiz então foi mudar o input para um select da seguinte forma:

    NO CONTROLLER
    ‘fluxo’ => array(
                        ‘fluxo’ => array(
                            ‘operator’ => ‘LIKE’,
                            ‘value’ => array(
                                ‘before’ => ‘%’, // opcional
                                ‘after’ => ‘%’ // opcional
                            )
                        )
                    ),

    NA VIEW
    echo $this->FilterForm->select(‘fluxo’, array(” => ‘Todos’, 0 => ‘Saída’, 1 => ‘Entrada’), array(‘escape’ => false));

    Pois assim, quando selecionar o “Todos” ele faz um like e vai trazer todos, o “0”  só quando tiver um zero e assim por diante.

    Porém por algum motivo:
    1) a opção “Todos” não aparece no filtro, apenas um campo vazio e as outras duas opções aparecem corretamente; e
    2) Ao printar o “$this->FilterResults->getConditions()” o retorno é um array vazio.

    Sem querer abusar da sua ajuda, você tem idéia do porque ele não funciona?

    Obrigado mais uma vez pela atenção e ajuda!

  • fael.ti

    Pedro, com esse plugin teria possibilidade de fazer um combobox utilizando o select?

  • Rafael,
    Para fazer o combobox: http://t.co/n2HNmgWn

  • Xerxes

    Olá. Você recebeu meu comentário do erro que estou tendo? Quando procura-se por algo contendo ”  jo´  “, sem os espaços e aspas, mas com o acento depois do jo, retona um erro.

  • Fael Ti

    a minha dúvida é como fazer uma combobox se o controller já foi carregado os filtros, por exemplo combobox estado e cidade… quando selecionar estado buscar os dados de cidades relacionado…

  • Entendi…
    Vou implementar um método que possibilite isso…

  • Patrick

    Ola Pedro, otimo plugin!!
    No entanto eu tenho uma url com alguns parametros, no padrao  http://localhost/foo/controller/action/params_pass/params_named:params_value
    ex :http://localhost/foo/users/index/Details/local:1

    E ai quando fazia uma busca estava quebrando essa url.
    A priori para resolver, adicionei na _redirectToNamedUrl logo antes de $this->controller->redirect($url, null, true); a linha abaixo

    $url = array_merge($this->controller->request->params[‘pass’],$url,$this->controller->request->params[‘named’]);

    Esta funcionando, mas nao sei se é a melhor maneira de manter os parametros da url.

    Muito obrigado e parabens,
    Patrick

  • Beleza Patrick?

    Esqueci completamente de passar também os parâmetros da aplicação. Que vacilo ^^’

    Desta forma, você terá problemas se utilizar mais de um filtro na mesma página. Antes do array_merge, você precisa remover os filtros do $this->controller->request->params[‘named’].

    Eu subi uma atualização do plugin com essa correção. Baixe novamente pelo GitHub.

    Abraços!

  • Recebi sim! =)
    Inclusive, acabei de subir uma atualização do plugin com a correção deste erro.

  • Patrick

     Ola Pedro, tudo beleza.

    Obrigado pela atualização.
    Já baixei e testei. Agora funcionou sem problemas.
    Solução mais inteligente que a minha! eheh

    Brigadão,
    Abraços,
    Patrick

  • Falaí Pedro… eu estava finalmente aplicando este teu plugin em meus sistemas mas me deparei com uma situação e não vi (ou não entendi) pela descrição de uso do plugin como resolvê-la.

    Eu tenho um sistema de filtro por palavra-chave (onde o usuário digita o termo a procurar) e uma lista de links com alguns termos possíveis a realizar o filtro (como categorias por exemplo).

    No primeiro caso, o FilterResults funciona, mas no segundo, a paginação não acompanha o formato da url que é modificada pelo Router, e assim deixa de funcionar a pesquisa.

    Digamos que a tabela de dados tenha mais um campo onde informe o nome do estado (apenas como exemplo):

    https://gist.github.com/3325363 

    Eu teria uma lista de links só com os estados e outra só com os nomes dos usuários e ambos seriam passados para a action index() do controller e seriam modificados pelo router para deixar a url mais “amigável”:

    https://gist.github.com/3325365 

    Assim a busca ficaria assim: http://meu_site/users/estado/sao_paulo

    O Problema é que o Paginate não reconhece a url modificada e transforma isso em http://meu_site/users/index/estado:sao_paulo e a busca para de funcionar.

    Com o FilterResults é possível efetuar esse tipo de busca também ou ele se limita apenas a campos de formulário?

  • Grande Gabriel! =D

    Entendi o problema. Faz o seguinte, manda pra mim os códigos do Controller e da View, vou simular exatamente o seu problema aqui e adaptar o plugin e/ou o seu código. ^^

    Abraços!

  • Jlima Spam

    Olá Pedro,
    Antes de mais obrigado pela sua disponibilidade em partilhar este plugin 😉

    Ao instalar o plugin deparei-me com um erro de memoria (Fatal error: Allowed memory size of XXXbytes exhausted (tried to
    allocate XX bytes) in …)

    Cheguei a colocar 512M no memory_limit do php.ini e continuei a ter o mesmo erro, quando a aplicação onde estou a testar tem estado a funcionar com 128M.

    Aos 512M parei de aumentar e optei por desligar o plugin Debug.Toolbar (não acredito que não conheças de qualquer forma aki fica https://github.com/cakephp/debug_kit/), e já funcionou.

    Tinhas conhecimento deste problema? Se sim, sabes ao que se deve?

    Obrigado

  • Você é o primeiro que reporta esse “problema”…

    No meu php.ini eu sempre deixo o padrão memory_limit = 128M

    Eu não sei o que possa ser… reduzi aqui para memory_limit = 32M e não tive nenhum problema.

  • Ótimo trabalho Pedro! Parabéns!

  • Pedro, gostaria de uma ajuda!
    Pode me falar quando o campo é datetime o que temos que fazer? Além de colocar a data, a hora deve ir também no BETWEEN.
    Aguardo seu retorno, obrigada!

  • Olá Camila!
    Na documentação em portugues existe instruções para BETWEEN em campos datetime. Veja este link: https://github.com/pedroelsner/filter_results/blob/2.0/README.pt-br.markdown#between

  • Pedrão, sensacional!!! Mais uma vez, parabéns!!!

    Obrigado por compartilhar conosco. Sem palavras.
    Abração. Tony.

  • Wilsonfalai

    Meus parabéns. Eu estava precisando de algo assim. Você pode me dar uma dica usando ajax(Jquery)? Já utilido o JqueryUI, no cake, porém preciso fazer uma busca com paginação em ajax.
    Ex: Tenho uma tela de cadastro e preciso de adicionar um cliente relacionado ao produto que estou cadastrando, abro uma janela flutuante com Dialog(Jquery), e pesquiso esse cliente com paginação, porém precisa ser com ajax.

    Parabéns novamente.

  • rfcore

    Hello!

    First of all this plugin is very useful! Thanks for sharing this.

    I have a problem though with making advanced filter work. https://github.com/pedroelsner/filter_results#advanced-filter

    Can you help me figure out what seems to be the problem?

    I carefully followed the instructions but it gives me an error with the following message:
    Error: Call to a member function getOption() on a non-object File: C:wampwwwappPluginfilter_resultsViewHelperFilterFormHelper.php Line: 224

    ——————————————————————————————————————————————
    UsersController.php
    …public function index() {     // Add filter    $this->FilterResults->addFilters(‘filter1’);        // Define conditions    $this->FilterResults->setPaginate(‘conditions’, $this->FilterResults->getConditions());    $this->User->recursive = 0;    $this->set(‘users’, $this->paginate()); }
    ——————————————————————————————————————————————

    View/Users/index.ctp

    echo $this->FilterForm->create();echo $this->FilterForm->selectFields(‘filter1’,        array(            ‘User.id’       => __(‘ID’, true),            ‘User.name’     => __(‘Name’, true),            ‘User.username’ => __(‘Username’, true),        ),        array(            ‘class’ => ‘select-box’        ));echo $this->FilterForm->selectOperators(‘filter1’,        array(            ‘LIKE’ => __(‘containing’, true),            ‘=’    => __(‘equal to’, true)        ));echo $this->FilterForm->input(‘filter1’);echo $this->FilterForm->end(__(‘Filter’, true));
    ——————————————————————————————————————————————

  • Hello Man,
    I just upgraded the plugin. =)
    Plese, download and try again.

  • Geazi

    Como que realiza isso pra checkbox?

  • Não entendi a pergunta… ^^’
    Manda um exemplo da sua dúvida.

  • Entendi.

    – Crie 2 checkbox. Um para cada valor.
    – Um será o filtro1 e o outro o filtro2. (ambos irão apontar para o mesmo campo no banco de dados nas configurações do filtro).
    – Depois, concatene os filtros com OR.

    Documentação: https://github.com/pedroelsner/filter_results/blob/2.0/README.pt-br.markdown#agrega%C3%A7%C3%A3o-de-filtros
    Exemplo (codigo): https://gist.github.com/4510729

    Espero ter ajudado.

  • Adriano Gobbo

    Eu fiquei com uma dúvida aqui… tem uma coisa que já percebi que boa parte dos desenvolvedores em cakephp ignora, o retorno para a index na pagina correta…
    Se estou numa index na página dois e clico para editar algo, quando eu retornar para a index, na minha opinião, o ideal é que se retorne na página correta… e eu tenho isso já resolvido no meu projeto, mas agora que estou utilizando o seu plugin, eu perdi isso… se eu faço um filtro e vou para a segunda página e clico no detalhe, se eu retorno ele volta na segunda página, mas sem o filtro… você tem idéia de como resolver isso?
    Se já houvesse algo do tipo estado inicial do filtro, se eu já der um valor para o filtro na criação da página ele realizasse o filtro acho que já matava a charada…
    Mas muito legal o seu plugin… muito útil… valeu!!!

  • Boa Andriano!

    Criarei uma SESSION sempre com o ultimo filtro para cada Action. Desta forma, ao retonar para ela, podemos ativa-lo, inclusive também guardando e restaurando a paginação,,,

    Irei implementar. =D
    Abraços!

  • Adriano Gobbo

    Você é demais cara… valeu! Como fico sabendo quando sair a atualização?

  • Ola, instalei o plugin certinho como dito ai em cima e testei o exemplo mais simples e esta me resultando esses erros

    Warning (2): array_merge(): Argument #1 is not an array [APP/Plugin/FilterResults/Controller/Component/FilterResultsComponent.php, line 1060]
    Warning (2): array_merge() [function.array-merge]: Argument #1 is not an array [APP/Plugin/FilterResults/Controller/Component/FilterResultsComponent.php, line 1060]
    Warning (2): array_merge() [function.array-merge]: Argument #1 is not an array [APP/Plugin/FilterResults/Controller/Component/FilterResultsComponent.php, line 1060]
    Strict (2048): Declaration of FilterFormHelper::submit() should be compatible with FormHelper::submit($caption = NULL, $options = Array) [APP/Plugin/FilterResults/View/Helper/FilterFormHelper.php, line 24]

  • Michael, qual é a versão do CakePHP?

  • estou utilizando a versão 2.3

  • Adriano Gobbo

    Estou usando a versão 2.3 também… vai nesse arquivo FilterFormHelper na linha 24 e inclui esse = NULL após $caption que funciona…

  • Michael,

    Tem certeza que não tem nada errado no seu Controller, quando você chama a função setPaginate do FilterResults?

    Acabei que instalar o Cake2.3, fiz meu tutorial até o Filtro Simples e funcionou sem problemas…

  • Adriano Gobbo

    É só colocar o = null que está faltando na linha indicada

  • Simplesmente não consigo reproduzir esse erro…
    Como assim Adriano? Em qual linha?
    Se me informar corretamente eu atualizo o plugin.
    Obrigado

  • Adriano Gobbo

    FormHelper::submit($caption = NULL, $options = Array) [APP/Plugin/FilterResults/View/Helper/FilterFormHelper.php, line 24]

  • Adriano Gobbo

    Ei cara, eu vi que você atualizou o plugin com recursos de paginate… é só botar aquele auto -> paginate = true que a mágica acontece? não precisa de mais nada?

  • Plugin atualizado.
    Obrigado!

  • Isso mesmo =)
    Estou implementando o sistema de sessão conforme você sugeriu para guardar os filtros, e aproveitando, para também alterar a estrutura do plugin e adicionar mais recursos.

  • Adriano Gobbo

    Mas já está funcionando? Por que eu posso esperar… rs tô fazendo outra melhoria no meu sistema ainda…

  • Sim, esta funcionando.
    A versão 2.1 vai mudar bastante coisa.
    Vou fazer um novo Post no site com todas as mudanças e ajustes necessários na configuração.

  • Adriano Gobbo

    Pedro, na nova versão você mudou o nome do componente e agora não está funcionando e eu não estou conseguindo fazer funcionar! Valeu ae

  • Adriano, não tem problema. Só tu alterar manualmente 🙂 Aqui funcionou corretamente.

    Pedro, tu pode me fazer um favor cara? Eu queria uma exemplo usando HABTM.

    Pq eu tenho uma tabela CONTEUDOS e uma tabela CATEGORIAS. E depois tenho uma terceira tabela relacionando elas CONTEUDOSCATEGORIAS 🙂

    Sacou?

    Fico no aguardo.

  • Saquei!

    Para isso, é só configurar os Models corretamente.

    Saca só esse tutorial: http://pedroelsner.com/2012/09/pesquisando-em-associacoes-habtm-no-cakephp/

    Abraços =P

  • Anderson Brandão

    Pedro, estou me deparando com um problema. Após carregar o plugin a aplicação sofre modificação e não aceita o charset iso-8859-1. Desde já agradeço.

    Ex: A��es ao invés de Ações.

  • Anderson,
    Isso é erro de codificação. Tente salvar o arquivo em UTF8 sem o BOM, e mudar a codificação para UTF8.