pre_get_posts: alterando a query padrão do WordPress

Recentemente tive uma necessidade em um projeto que me chamou atenção para uma dessas coisas do mundo WordPress: o hook pre_get_posts.

Em um projeto em que trabalhava existia um página chamada “agenda”, destinada a exibir todos os post-types do tipo “agenda”.

Usando o sistema de templates do WordPress tudo funcionaria perfeitamente bem apenas criando o arquivo “archive-agenda.php” com as famosas linhas do loop dentro dele.

Ao carregar no browser a hipotética URL “www.meusite.com/agenda” o tal arquivo seria chamado e traria uma coleção dos meus post-types “agenda”.

Isto é o sistema de templates do WordPress em funcionamento, muito legal mas nada de novo. Mas surgiu uma necessidade específica: como se tratava de posts de uma agenda, era necessário ordená-los pela data de início de cada evento. E também só trazer aqueles que tivessem essa data cadastrada (num campo customizado, caso você esteja se perguntando aonde).

E agora, como implementar isso, de forma que a query feita no banco de dados tivesse estes filtros “apendados”?

A resposta é através do hook pre_get_posts, que permite modificar a query principal antes dela ser executada, modificando assim os resultados que serão trazidos do banco.

Query principal?

Se esta expressão deixou você com dúvidas, é necessário parar pra explicar.

A “query principal” (ou main query) é a query criada dinamicamente pelo WordPress através dos dados que ele obtém das suas URL’s. É ela que faz, por exemplo, o conteúdo da nossa página de exemplo “www.meusite.com/agenda” ser uma coleção de posts do tipo “agenda” sem que você tivesse que digitar um único comando SQL para isso acontecer.

O WordPress, ciente de que cada projeto sempre tem necessidades específicas, criou alguns métodos para permitir aos desenvolvedores modificar a query principal. Um método bastante recomendado é usando o hook pre_get_posts().

Eis o que diz o Codex a respeito dele:

This hook is called after the query variable object is created, but before the actual query is run.

The pre_get_posts action gives developers access to the $query object by reference (any changes you make to $query are made directly to the original object – no return value is necessary).

Usando o pre_get_posts

Se você desenvolve para WordPress já deve estar familiarizado com a classe WP_Query e este tipo de código é comum para você:

$args = array(
    'meta_query' => array(
        array(
            'key' => 'data_inicio',
            'value' => date('Ymd'),
            'compare' => '>='
            )
        ),
    );

$custom_posts = new WP_Query( $args );

Geralmente usamos uma construção como essa para criar uma query secundária em determinada página, imprimindo os resultados em alguma área específica da mesma. Ou então para criar uma query totalmente nova numa página customizada, onde os resultados padrão do WordPress não nos interessem.

No meu caso não se tratava de uma coisa nem outra, eu queria continuar usando a query principal mas também adicionar alguns argumentos a ela – os tais filtros que mencionei no início do texto.

Através do pre_get_posts descobri que é possível fazer isso de uma forma limpa, sem ‘sujar’ os arquivos do template com lógica ou apelar para outras soluções macarrônicas.

No meu “functions.php” criei o seguinte código:


function filterAgenda( $query ) { if( is_admin() || !is_post_type_archive('agenda')) { return $query; } else { $query->set('meta_query', array( array( 'key' => 'data_inicio', 'value' => date('Ymd'), 'compare' => '>=' ) ); } return $query; } add_action('pre_get_posts', 'filterAgenda');

Agora, a hipotética página “www.meusite.com/agenda” apresentaria os resultados com os filtros aplicados, exatamente como eu queria.

Explicando

Se você subir um pouco a página poderá verificar que dentro da função filterAgenda() aproveitei o mesmo código que já havia passado no exemplo anterior com a classe WP_Query. Isto só para demonstrar que qualquer coisa que façamos com a WP_Query podemos fazer também no pre_get_posts.

Analisando a função, que é invocada através do hook pre_get_posts, podemos ver que ela recebe como parâmetro a variável $query. Esta variável representa (por referência) o objeto da query principal.

Após passarmos, dentro de $query->set(), nossos filtros em forma de array, basta retornarmos o tal objeto e a query será alterada.

No exemplo acima, que usei em meu projeto, usei o parâmetro meta_query, que permite adicionar valores de campos customizados e filtrar posts a partir destes valores. Neste caso estou dizendo que quero somente posts que possuam o campo data_inicio como data maior ou igual à atual.

Mas cuidado

É importante lembrar que antes de passar qualquer parâmetro devemos especificar qual página estamos alterando, pois por padrão as alterações vão valer pra todas. Acredite, você não vai querer ver o resultado disso :-) .

Esta definição de “queries-alvo” foi dada logo na primeira linha da função, para evitar qualquer mal-entendido:


if( is_admin() || !is_post_type_archive('agenda')) { return $query; // retorne sem alterar nada! } else { // alteracões aqui! return $query }

Pra bom entendedor, estamos especificando que as alterações devem ser aplicadas apenas nas queries que não forem para a área do admin e somente se a página for um archive do post-type “agenda”.

Ver pra crer

Se você tem curiosidade ou precisa ver as queries geradas pelo Wordpress, simplesmente acrescente esta linha em seu arquivo “wp-config.php”:

define('SAVEQUERIES', true);

Em seguida, em algum arquivo do projeto como o footer.php, inclua estas linhas:

if (SAVEQUERIES) {
echo "<!--\n"; print_r($wpdb->queries);
echo "\n-->\n";
}

Agora poderá ver todas as queries executadas no carregamento de uma página (são muitas!). Só note que a main query, aquela que alteramos aqui, fica no ali no meio e você pode ter que procurar um pouco 😉 .