Prendre Rendez-vous
Logo Olympe Studio blanc

Comment utiliser WP Query dans Wordpress ?

WP Query est une classe de WordPress qui permet d’effectuer des requêtes à la base de données. Elle permet aux développeurs de récupérer les données de leur choix au sein d’un thème ou d’un plugin.

Si vous êtes développeur WordPress, vous serez très souvent confronté à un besoin de données spécifiques et c’est cette classe qui reviendra à chaque fois.

WP Query supercharge également la « loop » wordpress pour vous permettre d’afficher directement des éléments avec les fonctions comme the_content ou the_permalink.

Dans ce guide, on va s’intéresser à l’usage de cette fonctionnalité pour le développement d’un thème wordpress, si ce n’est pas déjà fait, je vous invite à mettre en place un thème de votre côté avant de continuer.

Sommaire

WP Query, l’ORM de WordPress ?

Un ORM (Object Relational Mapping) est une interface (sous forme d’objet ou de fonctions) qui vous permet d’interagir avec une base de données en utilisant un langage de programmation plutôt qu’avec des requêtes SQL.

C’est très pratique parce qu’ils implémentent en général tout un tas de vérification qui permet à des débutants de faire des requêtes en toute sécurité.

Vous n’avez ainsi pas à trop vous préoccuper des problèmes de sécurité quand vous interagissez avec la base de données.

C’est également un moyen plutôt lisible de réaliser des requêtes de données et de les utiliser, comme vous pouvez le voir dans l’exemple ci-dessous :

<?php

$queryResponse = new WP_Query([
  'post_type' => 'post',
  'posts_per_page' => 10
]);

if ($queryResponse->have_posts()) :
  // En utilisant `the_post()`, on modifie temporairement la valeur de la global $post.
  while ($queryResponse->have_posts()) : $queryResponse->the_post();
    ?>
    // On peut ainsi utiliser les fonctions de templating avec le post de la boucle en court.
    <h2><?php the_title(); ?></h2>
    <div><?php the_content(); ?></div>
    <?php
  endwhile;
endif;

// On remet la global $post à son état initial.
wp_reset_postdata();

On note plusieurs choses importantes ici :

  • WP Query ne se contente pas de vous renvoyer des objets PHP après avoir faire une requête à la base de données, il vous permet également de modifier le comportement de WordPress dans les templates.
  • La Query utilise un argument de type `array` qui permet de configurer votre query de manière bien plus simple à comprendre qu’avec une requête SQL classique.
  • La réponse est renvoyée sous la forme d’un objet WP Query qui apporte son lot de méthodes utilitaires comme « have_posts » qui vous permet d’effectuer des vérifications basiques de manière simple et lisible.

Ces fonctionnalités vous permettent ainsi de récupérer des données à la volée et d’insérer du contenu dynamique dans vos pages, d’une manière assez simple.

Le problème avec WP Query

J’attire votre attention sur le plus gros inconvénient de cette classe.

Plus précisément, lorsqu’on en fait un usage comme dans l’exemple ci-dessous, et qu’on cloture son utilisation avec la fonction : wp_reset_postdata();

Cette fonction, avec un nom pas spécialement clair sur ses vraies fonctionnalités, est LE gros défaut de WP Query selon moi.

En effet, si vous utilisez une boucle avec $query->the_post(), pour afficher des éléments sur votre page, vous modifiez la variable global $post qui contient les données du post sujet à la requête initiale.

Par exemple, si vous vous trouvez sur la page d’accueil, la global $post contient les données de la page désignée comme page d’accueil.

Lorsque vous allez utiliser $query->the_post(), vous allez remplacer cette variable globale par le port de l’itération en court.

Et ça, c’est vraiment très très (très) étrange, et ça peut conduire à des bugs difficiles à résoudre. C’est d’autant plus étrange que c’est la méthode recommandée par WordPress de procéder.

D’accord, mais vous allez un peu loin, il suffit d’utiliser la fonction wp_reset_postdata après la boucle après tout.

Effectivement, l’usage de wp_reset_postdata résout ce problème, mais on est très loin des fonctionnalités d’un ORM.

WP Query est un « Query Builder » agrémenté d’une dose de fonctionnalités qui n’ont pas grand-chose à faire là.

WP Query crée beaucoup de problèmes, dont les suivants :

  • Sa responsabilité n’est pas uniquement de réaliser plus facilement des query à la base de données, c’est aussi d’aider à leur lecture… À leur injection dans l’HTML… Etc.
  • Elle incite à mélanger le PHP au rendu HTML, et donc, à désorganiser votre code,
  • L’usage de la fonctionnalité est inconsistant, pourquoi wp_reset_postdata() n’est pas une méthode de WP_Query ? Pourquoi tout mélanger et rendre l’usage confus ?

En tant que développeur, vous ne voulez pas d’un code désorganisé et sujet à des effets de débordement parfois complexe à deboguer.

Mes recommandations sont les suivantes vis-à-vis de cette classe :

  • N’utilisez jamais WP_Query directement dans un template. D’ailleurs, évitez le templating PHP de base, optez plutôt pour des solutions alternatives comme Timber, ou un framework frontend.
  • Quand vous faites une WP_Query, n’utilisez jamais la « loop » : while $q->have_posts(): $q->the_post();
  • Servez-vous de WP Query uniquement pour lire des données dans la base de données.

WordPress est un outil très puissant, y compris pour les développeurs, mais il faut garder en tête que ses conventions de code sont parmi les plus mauvaises du monde opensource, et que la plupart des recommandations faites dans la documentation sont purement et simplement mauvaises.

En tant que bon développeur, c’est de votre devoir de créer vos propres conventions lorsque vous utilisez les outils mis à votre disposition.

S’interdire un mal, c’est souvent pour le meilleur !

Comment réaliser des query avec WP_Query ?

Utilisons WP_Query pour ce qu’il fait de mieux : construire des requêtes sécurisées pour lire des données dans la base de donnée.

Reprenons notre précédent exemple :

<?php

$queryResponse = new WP_Query([
  'post_type' => 'post',
  'posts_per_page' => 10
]);

On apprend ici deux choses importantes :

  • On peut récupérer des posts en fonction de leur type : post, page, votre_custom_post_type.
  • On peut gérer la pagination des posts directement dans la query avec posts_per_page qui définie le nombre de résultat par page et paged, qui défini le numéro de page.

La pagination

Intéressons-nous de plus près à la pagination.

On vient de le voir, il est possible de paginer nos requêtes, de manière à récupérer une partie seulement de nos posts.

Par exemple, si vous ne voulez afficher que 20 posts sur votre page de blog, et laisser les suivants en accès libre via la pagination, vous pourrez utiliser posts_per_page => 20 et pages => $currentPageNumber.

WP_Query, en fonction de la valeur de $currentPageNumber (récupérable via les fonctionnalisés de pagination native de WordPress), effectuera tout seul la bonne requête à la base de donnée en fonction de la page demandée par l’utilisateur.

<?php

// Récupère les 20 derniers posts publiés.
$queryResponse = new WP_Query([
  'post_type' => 'post',
  'posts_per_page' => 20,
  'paged'  => 1
]);


// Récupère les 20 derniers posts publiés, à partir de la page 2.
// (il oublie donc les 20 premier de la page 1)
$queryResponse = new WP_Query([
  'post_type' => 'post',
  'posts_per_page' => 20,
  'paged'  => 2
]);

Offset et Limits

Parfois, sans se trouver sur une page disposant de la pagination, on a besoin de récupérer des données dans un nombre limité, ou avec un décalage de départ.

Dans WordPress, il n’y a pas de paramètre limit pour limiter le nombre de résultats retournés.

Pour cela, on utilisera toujours post_per_page. (ce qui est dommage, mais soit).

Cela dit, il est possible de définir un offset :

<?php

// Récupère les 2 derniers posts publiés, mais sans le tout premier.
$queryResponse = new WP_Query([
  'post_type' => 'post',
  'posts_per_page' => 2,
  'offset'  => 1  // On passe le dernier post publié.
]);

Par défaut, WP_Query utilisera la valeur spécifié dans la variable de configuration « Blog pages show at most » (que vous pouvez lire avec get_option( 'posts_per_page')). Vous pouvez spécifiez 'posts_per_page' => -1 pour récupérer toutes les entrées disponibles.

Order et Orderby

Par défaut, les résultats sont rangés par date de publication, du plus récent au plus ancien. Il est possible de modifier ce comportement avec les directives order et orderby.

orderby définit le champ que vous voulez sélectionner pour l’ordre des résultats, par exemple on pourrait vouloir récupérer nos articles de blog par ordre alphabétique :

<?php

// Récupère les 2 derniers posts publiés, mais sans le tout premier.
$queryResponse = new WP_Query([
  'post_type' => 'post',
  'orderby' => 'title',
  'order' => 'ASC'  
]);

order permet de définir le sens du tri. Ses valeurs sont toujours ASC ou DESC.

Query sur les attributs

Il est possible de sélectionner des publications par leurs attributs, c’est à dire les données stockées :

  • ID de la publication,
  • ID de l’auteur,
  • Status de publication,
  • etc.

Il s’agit des requêtes les plus courantes. C’est en effet très classique de récupérer les derniers articles d’un auteur, de rechercher un article par son identifiant ou encore de vouloir lister tous les brouillons de la base de données.

Voici quelques exemples :

<?php

// Récupère le post avec l'ID 114
$queryResponse = new WP_Query([
  'ID' => 114
]);

// Récupère les 10 derniers post de l'auteur avec l'ID 4
$queryResponse = new WP_Query([
  'author' => 4,
  'posts_per_page' => 10
]);

// Récupère tous les brouillons disponibles.
$queryResponse = new WP_Query([
  'post_status' => 'draft',
  'posts_per_page' => -1
]);

Requête par post_type, catégories ou tags

WP Query peut également filtrer les résultats par le type de publication (qui se trouve être un attribut, comme les exemples ci-dessus), mais aussi, et surtout, par les relations avec les autres jeux de données.

Il n’est pas rare de vouloir récupérer tous les posts attaché à une catégorie ou disposant d’un tag en particulier :

<?php

// Récupère les post de type 'news' disposant du tag 'featured' ET étant dans la catégorie 'france'.
$queryResponse = new WP_Query([
  'post_type' => 'news',
  'category_name' => 'france',
  'tag' => 'featured'
]);

Relation et Tax_Query

Les Catégories et les Tags sont des taxonomies spéciales pour WordPress, c’est pour cela qu’elles ont leur place à part entière dans les paramètres acceptables de WP Query.

Cependant, vous pourriez être amené à créer vos propres taxonomies avec ses propres terms. Par exemple, si vous créez une agence immobilière, vous pourriez ranger vos annonces par types : vente, location ou location courte durée.

Pour cela, vous auriez besoin d’une taxonomie personnalisée.

Pour récupérer vos annonces, vous devrez alors utiliser une tax_query :


<?php

// Récupère toutes vos annonces de location courte durée.
$queryResponse = new WP_Query([
  'post_type' => 'real_estate',
  'tax_query' => [
    [
      'taxonomy' => 'estate_type',
      'field'    => 'slug',
      'terms'    => 'short_term_rent'
    ]
  ]
]);

// Récupère toutes vos annonces de location (courte ou longue durée)
$queryResponse = new WP_Query([
  'post_type' => 'real_estate',
  'tax_query' => [
    [
      'taxonomy' => 'estate_type',
      'field'    => 'slug',
      'terms'    => ['short_term_rent', 'long_term_rent']
    ]
  ]
]);

Vous noterez ici, qu’il faut spécifier le champ sur lequel vous voulez appliquer la requête par taxonomie. Ce champ doit être l’un des suivants : slug, ou term_id, term_taxonomy_id ou name.

Pour rendre votre requête lisible, je vous recommande d’utiliser le slug plutôt que l’ID quand c’est possible. (bien entendu, utilisez ce qui se prête le mieux à votre situation si vous avez des variables).

Il est également possible de combiner vos requêtes :


<?php

// Récupère toutes vos annonces de vente 
// ET qui sont situé dans la ville de Lyon
$queryResponse = new WP_Query([
  'post_type' => 'real_estate',
  'tax_query' => [
    [
      'taxonomy' => 'estate_type',
      'field'    => 'slug',
      'terms'    => 'for_sale'
    ]
    [
      'taxonomy' => 'city',
      'field'    => 'slug',
      'terms'    => 'lyon'
    ]
  ]
]);


<?php

// Récupère toutes vos annonces de vente OU de location longue durée 
// ET situées à Lyon
$queryResponse = new WP_Query([
  'post_type' => 'real_estate',
  'tax_query' => [
    'relation' => 'AND',
    [
      'relation' => 'OR',
      [
        'taxonomy' => 'estate_type',
        'field'    => 'slug',
        'terms'    => 'long_term_rent'
      ],
      [
        'taxonomy' => 'estate_type',
        'field'    => 'slug',
        'terms'    => 'for_sale'
      ]
    ],
    [
      'taxonomy' => 'city',
      'field'    => 'slug',
      'terms'    => 'lyon'
    ]
  ]
]);

Les meta queries

Tout comme pour les taxonomies, il est possible d’effectuer des requêtes sur la relation entre les posts et les postmeta.

Pour cela on utilise les meta_query et ça ressemble beaucoup au tax_query dans la mise en oeuvre.

Si vous utilisez ACF (Advanced Custom Fields), et vous devriez l’utiliser, alors ce type de requête sera parfois amené à montrer le bout de son nez.

<?php

// Récupère les post dont la meta 'color' a la valeur 'blue'
$queryResponse = new WP_Query([
  'post_type' => 'post',
  'meta_query' => [
    [
      'key' => 'color',
      'value' => 'blue',
      'compare' => '='
    ]
  ]
]);

Une meta query ne peut avoir que les 4 paramètres suivants :

  • key : La clé de la méta.
  • value : La valeur attendu de la méta.
  • compare : Comment doit être comparé la méta ?
  • type : Quel est le type de la valeur ?

attardons nous sur compare et type.

Compare ne peut avoir que les valeurs suivantes, en premier, les plus simples, et proches du langage humain :

  • = (Égal à)
  • != (Différent de)
  • > (Supérieur à)
  • >= (Supérieur ou égal à)
  • < (Inférieur à)
  • <= (Inférieur ou égal à)

et les plus proche du SQL :

  • LIKE (Semblable à %LIKE% en SQL)
  • NOT LIKE (Inverse de %LIKE% en SQL)
  • IN (la valeur méta EST INCLUE dans le tableau de valeurs)
  • NOT IN (la valeur méta n’est PAS dans le tableau de valeurs IN)
  • BETWEEN (BETWEEN; la valeur méta est entre deux valeurs)
  • NOT BETWEEN (NOT BETWEEN; la valeur méta n’est pas entre deux valeurs
  • EXISTS (Vérifie si la clé méta existe, indépendamment de sa valeur)
  • NOT EXISTS (Vérifie si la clé méta n’existe pas, indépendamment de sa valeur)

Dans le cas de type, on aura les possibilité suivantes :

  • CHAR (Chaîne de caractères)
  • NUMERIC (Nombre, entier ou à virgule flottante)
  • BINARY (Chaîne binaire)
  • DATE (Date)
  • DATETIME (Date et heure)
  • DECIMAL (Nombre décimal)
  • SIGNED (Entier signé)
  • TIME (Heure)
  • UNSIGNED (Entier non signé)

C’est un peu compliqué comme ça, mais retenez simplement ceci :

type permet de s’assurer que la comparaison est faite avec les bons types, ce qui permet d’éviter les erreurs potentielles.

compare permet de spécifier la comparaison. En général, on utilise les comparaisons algébriques, parce qu’elles sont plus lisibles.

Dans les deux cas, l’utilisation sera relativement évidente au moment de la requête.

Exemple de requête pour ACF

On l’a dit, ACF est un incontournable du développement WordPress. Il est courant de devoir réaliser des WP Query sur des champs spécifiques, voici quelques exemples :

<?php

// Liste des posts mis en avant
$queryResponse = new WP_Query([
  'post_type' => 'post',
  'meta_query' => [
    [
      'key' => 'featured_post', // Champ ACF True/False
      'value' => '1', // Les booléens dans ACF sont sous forme d'entier 0 ou 1.
      'compare' => '='
    ]
  ]
]);

// Liste d'événements entre deux dates.
$queryResponse = new WP_Query([
  'post_type' => 'event',
  'meta_query' => [
    [
      'key' => 'event_date', // ACF date field
      'value' => ['2023-01-01', '2023-12-31'],
      'type' => 'DATE',
      'compare' => 'BETWEEN'
    ]
  ]
]);

// Liste d'événements entre deux dates.
$queryResponse = new WP_Query([
  'post_type' => 'event',
  'meta_query' => [
    [
      'key' => 'event_date', // ACF date field
      'value' => ['2023-01-01', '2023-12-31'],
      'type' => 'DATE',
      'compare' => 'BETWEEN'
    ]
  ]
]);

// La liste des fruits qui sont blue, rouge ou vert.
$queryResponse = new WP_Query([
  'post_type' => 'fruits',
  'meta_query' => [
    [
      'key' => 'color',
      'value' => ['blue', 'red', 'green'],
      'compare' => 'IN'
    ]
  ]
]);

Avec ACF, ce sera quasiment toujours des meta_query. Plutôt simple n’est-ce pas ?

Les recherches

WP Query permet de réaliser des recherches avec le paramètre s.

On vous montre pour que vous le sachiez, mais retenez bien que la recherche de WordPress est vraiment très très (très) nulle.

Si vous avez la possibilité, utilisez plutôt un outil d’instant search comme Meilisearch qui apporte des résultats stupéfiants tout assurant une expérience utilisateur aux petits oignons.

Mais revenons en à WP Query, voici comment ça se passe :

<?php

// Recherche des posts avec le mot clé "world".
$queryResponse = new WP_Query([
  'post_type' => 'post',
  's' => 'world'
]);

Requête de dates

WordPress offre également la possibilité de faire des requêtes simplifiées pour les dates. On peut par exemple retrouver des posts entre deux intervals de temps donnés :

<?php

// Tous les posts publiés le 15 Juin 2021.
$queryResponse = new WP_Query([
  'date_query' => [
    [
      'year'  => 2021,
      'month' => 6,
      'day'   => 15,
    ],
  ],
]);

// Tous les posts publiés APRÈS 01 Avril 2021.
$queryResponse = new WP_Query([
  'date_query' => [
    [
      'after' => 'April 1st, 2021',
      'inclusive' => false,
    ],
  ],
]);


// Tous les posts publiés entre Janvier et Juillet 2021.
$queryResponse = new WP_Query([
  'date_query' => [
    [
      'after' => [
        'year'  => 2021,
        'month' => 1,
        'day'   => 1,
      ],
      'before' => [
        'year'  => 2021,
        'month' => 6,
        'day'   => 30,
      ],
      'inclusive' => true,
    ],
  ],
]);

Utiliser l’instance WP Query après la requête

Après avoir effectué la requête, WordPress vous retourne une instance de WP Query qui vous offre plusieurs possibilités.

En premier lieu, il y a la boucle, mais, comme on l’a dit plus haut, évitez de l’utiliser à tout prix.

Vous pouvez cependant lire les propriétés publiques suivantes :

  • $posts: La liste des posts retournés par la base de données.
  • $found_posts: Un booléen vous indiquant si la query a trouvé des posts.
  • $max_num_pages: Le nombre maximum de pages disponibles avec cette query.
  • $request: La requête SQL telle qu’elle a été construite et envoyée.

Ces 4 propriétés sont indépendantes de la boucle et vous permettent d’utiliser votre réponse avec sécurité.

Il est de bon ton de vérifier avec $found_posts que la requête a bien aboutie avec des résultats avant d’essayer d’itérer $posts.

La mise en cache des queries

WP Query n’est pas parfait.

La boucle par exemple n’a, en mon sens, pas lieu d’exister et c’est pourtant une très grosse partie des fonctionnalités disponibles. Mais après tout, personne ne vous oblige à l’utiliser.

Cependant tout ce passe par WP Query dispose d’une mise en cache efficace qui vous permet d’éviter la duplication des requêtes au sein d’une même exécution de votre application.

Ainsi, vous devez TOUJOURS utiliser WP Query (ou les fonctions construites par-dessus) afin de faire des requêtes à la base de données.

Non seulement WordPress lui-même optimise énormément les requêtes à la base de donnée grâce à cette classe, mais beaucoup de plugins tiers reposent sur l’usage de cette classe pour optimiser encore davantage la vitesse de chargement de vos pages.

C’est par exemple le cas des extensions qui utilisent Redis pour mettre en cache les requêtes.

Par pitié, n’écrivez jamais de SQL brut dans vos thèmes ou plugins. Utilisez toujours les API à votre disposition afin d’assurer les meilleures performances.

Conclusion

WP_Query est un outil essentiel dans le développement de thèmes et de plugins pour WordPress.

Bien que ne soit pas un ORM à proprement parler, il fonctionne comme un builder de requête SQL puissant, offrant une interface plus intuitive pour interagir avec la base de données. Cette facilité d’utilisation, cependant, vient avec ses propres défis et complexités.

L’un des principaux avantages de WP_Query est sa capacité à simplifier la récupération et la manipulation de données. Que ce soit pour filtrer des contenus par type, par date, par auteur, ou encore par métadonnées, WP_Query offre une flexibilité remarquable.

Les fonctionnalités intégrées telles que la pagination et la gestion des taxonomies facilitent grandement le développement d’application personnalisées.

Cependant, l’utilisation de WP_Query peut parfois conduire à des confusions, notamment en ce qui concerne la modification de la variable globale $post et la nécessité d’utiliser wp_reset_postdata().

Cette particularité peut être source d’erreurs et exige une attention particulière de la part des développeurs. Nous vous recommandons de ne pas utiliser cette fonctionnalité et de la fuir comme la peste, et également, de séparer votre logique PHP du rendu HTML pour maintenir un code clair et organisé.

La mise en cache intégrée de WP_Query améliore les performances en évitant les requêtes redondantes. Il est crucial d’utiliser WP_Query ou les fonctions basées sur celle-ci pour toute interaction avec la base de données, afin de bénéficier des optimisations de performance et de la compatibilité avec les plugins tiers.