Você está visualizando atualmente WordPress: o que você precisa saber sobre WP_Query e query_posts()

WordPress: o que você precisa saber sobre WP_Query e query_posts()

  • Última modificação: 15/02/2019
  • Tempo de leitura: 11 min.

O WordPress é um CMS e, por isso, a manipulação do conteúdo é um dos principais pilares da ferramenta. Neste post você entenderá o processo usado pelo WP para buscar o que precisa ser exibido e também como é possível alterar este processo.

O objetivo deste texto não é nem de longe substituir a documentação oficial,  que sempre deve ser consultada. Seu objetivo é colocar em um só lugar o que os iniciantes devem saber antes de começar a procurar por mais informações.

O que é uma query do WordPress?

Query em inglês significa consulta e normalmente é o termo usado para se referir a uma instrução em SQL (Structured Query Language, ou seja, Linguagem para Consultas Estruturadas). No WordPress chamamos de queries as consultas por conteúdos feitas através da classe WP_Query.

O que é WP_Query?

WP_Query é a classe PHP por trás de todas as buscas por conteúdo no WordPress. Ela aceita vários parâmetros em sua construção, incluindo post types, meta dados, taxonomias e datas, além de possibilitar a criação de parâmetros personalizados.

A classe WP_Query é acionada em todas as requisições feitas ao seu site, excetuando talvez raríssimas exceções de endereços controlados por plugins que interrompam o fluxo normal. Toda vez que chamamos uma página, um post, um termo de taxonomia e etc. o WordPress interpreta o endereço pedido através da Rewrite API e cria uma WP_Query baseada nos argumentos interpretados.

Conditional Tags e WP_Query

Funções como is_home, is_front_page, is_page, is_search e etc. são apenas chamadas simplificadas para $query->is_home(), $query->is_front_page() e assim por diante. Isto significa duas coisas: sem saber você esteve manipulando uma instância da classe WP_Query o tempo todo e que, se você tiver uma variável deste tipo, você também pode executar estes testes com elas.

Criando seus próprios loops: WP_Query e get_posts()

Quando nos referimos ao Loop no WordPress estamos falando sobre iteração (sem o n) pelos posts selecionados. Normalmente só temos um loop, que sempre se assemelha ao código abaixo:

if( have_posts() ) {
	while( have_posts() ) { the_post();
		// Estamos no loop
		the_title(); // Exibe o título. Esta função só deve ser usada dentro do Loop
	}
}

Se existem posts, itere sobre eles. A primeira instrução dentro do while é the_post(), que diz para o WordPress colocar as variáveis do próximo post do loop na variável global $post, através do método setup_postdata() da classe WP_Query.

Esta é a aparência do loop principal, ou seja, da iteração pelo conjunto de posts que o WordPress encontrou ao decodificar a requisição e buscar pelos conteúdos correspondentes. Se precisarmos de um outro conjunto de posts (conteúdo relacionado, conteúdos de uma determinada categoria fora de seu arquivo ou posts publicados depois do que está sendo exibido, por exemplo) podemos fazer isso de duas formas: criando um novo objeto WP_Query ou chamando a função get_posts(). As duas fazem a mesma coisa, mas retornarão para você coisas diferentes.

Criando um loop com WP_Query

Um exemplo simples de como criar um loop com WP_Query:

/// vamos criar uma query para pegar 3 posts da categoria novidades:
$minha_query = new WP_Query( array( 
	'posts_per_page' => 3,
	'category_name' => 'novidades',
) );

if ( $minha_query->have_posts() ) {
	echo '<ul>';
	while ( $minha_query->have_posts() ) {
		$minha_query->the_post();
		echo '<li>' . get_the_title() . '</li>';
	}
	echo '</ul>';
	wp_reset_postdata();
} 

Repare como o teste e a iteração sobre have_posts() continuam lá, assim como the_post(), mas dessa vez como métodos da variável que criamos.

A função wp_reset_postdata() serve para restaurar a variável global $post para seu estado anterior à nossa query, ou seja, ela colocará de volta na global $post o post da global $wp_query, que veremos mais pra frente.

Criando um loop com get_posts()

O mesmo exemplo usando a função get_posts():

$novidades = get_posts( array( 
	'posts_per_page' => 3,
	'category_name' => 'novidades',
) );

if ( count( $novidades ) ) {
	echo '<ul>';
	foreach( $novidades as $novidade ) {
		echo '<li>' . get_the_title( $novidade->ID ) . '</li>';
	}
	echo '</ul>';
}

A função get_the_title() aceita como parâmetro um post diferente do que está na variável global $post. Se você precisar usar a função the_content(), por exemplo, será preciso alterar a global, da seguinte forma:

$novidades = get_posts( array( 
	'posts_per_page' => 3,
	'category_name' => 'novidades',
) );

global $post;
if ( count( $novidades ) ) {
	echo '<ul>';
	foreach( $novidades as $post ) { // repare que $novidade virou $post
		setup_postdata( $post );
		echo '<li>';
		the_title( '<h2>', '</h2>' ) ; // os parâmetros de get_the_title e the_title são diferentes
		the_content();
		echo '</li>';
	}
	wp_reset_postdata();
	echo '</ul>';
}

Diferença entre WP_Query e get_posts()

A diferença básica é ao que você terá acesso. Usando WP_Query você tem acesso à quantidade de posts encontrados no total ($query->found_posts) e ao número de páginas possíveis ($query->max_num_pages), por exemplo. A função get_posts() por outro lado traz alguns argumentos já preenchidos além de, por padrão, ser ligeiramente mais rápida: ela passa o parâmetro no_found_rows como verdadeiro, evitando que a query final tenha a opção SQL_CALC_FOUND_ROWS e, com isso, dispensando o MySQL de passar a quantidade total de linhas encontradas.

Globais $wp_query e $wp_the_query

Neste processo de interpretar a requisição e gerar o objeto da classe WP_Query o WordPress cria duas variáveis globais: $wp_query, que provavelmente você já viu em algum lugar, e $wp_the_query, que será sempre a cópia original do que foi solicitado pelo usuário. Se alguém mexer em $wp_query, pelo menos podemos recuperá-la ao seu estado normal com $wp_the_query. É exatamente isso que a função wp_reset_query() faz.

Por que não se deve usar query_posts() nos arquivos do tema?

Este aqui é o fluxo padrão de uma requisição a um endereço do seu site fora do painel:

Você não deve usar query_posts() porque ela já foi chamada antes. Ao chamá-la de novo você duplicará o processamento (pedindo ao WordPress que faça uma outra consulta) e, além disso, ignorará potencialmente o que alguns plugins já tenham usado.

A paginação é outro exemplo de como usar query_posts() pode atrapalhar o seu trabalho. Se você desejar exibir 15 posts por página em uma determinada categoria e tentar sobrescrever a quantidade padrão do WP através de query_posts() você terá uma situação onde a primeiro item da segunda página deveria ser o 16º, mas na verdade é só o 11º.

Como alterar a query principal? Use pre_get_posts

Usar query_posts() é desaconselhável há algum tempo. De lá pra cá, muitos tem explicado o porquê e incentivado os desenvolvedores a usar a action pre_get_posts, com destaque especial para o Leo Baiano, que apelidou a action carinhosamente de preguetinho (o uso de trocadilhos com o preguetinho do Leo Baiano está autorizado nos comentários).

O uso é simples. Para eliminar a paginação na busca, por exemplo, você pode usar o seguinte código no functions.php do seu tema:

function altera_query_principal ( $query ) {
	if ( ! is_admin() AND $query->is_main_query() AND $query->is_search() ) {
		$query->set( 'posts_per_page', -1 );
	}
}
add_action( 'pre_get_posts', 'altera_query_principal' );

O código atribui a sua função ao hook pre_get_posts, então sua função será executada toda vez que uma query for processada. Dentro dela precisamos ter certeza de que vamos trabalhar apenas nas queries necessárias, então testamos se realmente estamos na parte “da frente” do site (dessa forma não atrapalhamos nada do painel), se estamos na query principal da página, isto é, se a query em questão é $wp_the_query e se esta query é de uma busca. Se isto tudo for verdade alteramos o atributo posts_per_page para -1, que significa não ter limite de posts.

Outro exemplo: excluir uma categoria da lista de posts do blog

function excluir_categoria( $query ) {
	if ( ! is_admin() AND $query->is_main_query() AND $query->is_home() ) {
		$query->set( 'cat', '-1,-1347' ); // Use um sinal de menos e o id da categoria OU use tax_query
	}
}
add_action( 'pre_get_posts', 'excluir_categoria' );

Se você precisar de muitas alterações você pode juntar todas em uma única função:

function altera_query_principal ( $query ) {
	if ( ! is_admin() AND $query->is_main_query() ) {
		if ( $query->is_search() )  {
			$query->set( 'posts_per_page', -1 );
		} elseif ( $query->is_home() ) {
			$query->set( 'cat', '-1,-1347' );
		} elseif ( $query->is_tax( 'portfolio' ) ) { // Exibe os posts por ordem alfabética no arquivo da taxonomia portfolio
			$query->set( 'orderby', 'title' );
			$query->set( 'order', 'ASC' );
		}
	}
}
add_action( 'pre_get_posts', 'altera_query_principal' );

Conclusão

Assim como os hooks, saber usar e manipular as queries do WordPress é essencial para quem desenvolve no CMS. Se o post foi útil não esqueça de compartilhar e deixar seu comentário 🙂

Felipe Elia

Associate Director of Platform Engineering na 10up, WordPress Core Contributor, Global Polyglots Mentor na comunidade internacional do WordPress e Locale Manager na comunidade WordPress Brasil.

Este post tem 4 comentários

  1. Saulo Padilha

    Existe algum limite de WP_Query que pode ser usado em uma mesma página?

    Imagine uma homepage que mostre os últimos posts de vários tipos de posts e taxonomias diferentes. Fazer uma WP_Query para cada um desses “blocos” dessa página é a forma mais correta?

    1. Felipe Elia

      Não existe limite, Saulo, pode usar a vontade. Como muito provavelmente você não vai precisar de paginação nem saber quantos foram encontrados ao todo pode usar get_posts ou WP_Query com o parâmetro no_found_rows que comentei no post para acelerar um pouco as coisas.

  2. Claudio Myst

    Maravilhoso seu artigo amigo, parabéns.
    Certamente muito vão se beneficiar com suas ricas informações.

  3. Leo Baiano

    Muito bem explicado, show de bola!

Comentários encerrados.