Chapitre 17 sur 20

Requêtes avancées avec WP_Query et WPDB

Laisser un commentaire

La plupart du temps, nous travaillons avec des fonctions nous permettant de simplement récupérer le contenu dont nous avons besoin : la loop, get_posts, get_comments, get_users, get_taxonomies, etc. Toutes ces fonctions peuvent recevoir des paramètres permettant de définir des critères de recherche.

Cependant, dans certains cas, nos besoins sont plus spécifiques : recherche ou filtrage complexe, requête sur plusieurs tables ou sur une table custom… C’est la qu’interviennent les custom queries et l’objet WPDB !

Requêtes avancées avec WP_Query

C’est l’objet que WordPress utilise sous le capot lorsque vous invoquez have_posts, get_posts, etc. De ce fait, les options de filtres offertes par l’ensemble des fonctions de récupération de données de WordPress découlent de WP_Query.

Ainsi, la plupart du temps, vous pourrez utiliser l’API de WP_Query avec les fonctions classiques. On se rabattra sur cette dernière seulement si la requête n’est pas réalisable avec les fonctions plus spécifiques. Par ailleurs, WP_Query dispose de nombreuses méthodes pour récupérer les données ou agir sur la requête.

Par exemple, get_posts nous permet de récupérer une liste de posts. dont on peut spécifier l’ordre et filtrer par de nombreux critères : liste d’auteurs, de catégories ou de tags à inclure ou exclure, intervale de dates, etc.

// On requêtes tous les posts dont le slug de la taxonomie "movie_genre" est "action" ou "comedy"
// ET dont les acteurs (taxonomie) ne contiennent pas les acteurs d'ID 103, 115 et 206
$query = new WP_Query([
    'post_type' => 'post',
    'tax_query' => [
        'relation' => 'AND',
        [
            'taxonomy' => 'movie_genre',
            'field'    => 'slug',
            'terms'    => ['action', 'comedy']
        ],
        [
            'taxonomy' => 'actor',
            'field'    => 'term_id',
            'terms'    => [103, 115, 206],
            'operator' => 'NOT IN'
        ]
    ]
]);

On aperçoit ici la puissance des requêtes. Mais on peut faire bien plus. Il est possible de requêter via des métas et aussi d’imbriquer les conditions sur les taxonomies.

// Récupère les biens immobiliers de type maison
// avec une superficie comprise entre 150 et 200m2
$query = new WP_Query([
    'post_type' => 'property',
    'tax_query' => [
        [
            'taxonomy' => 'type',
            'field'    => 'name',
            'terms'    => ['house']
        ]
    ],
    'meta_query' => [
        'key' => 'area',
        'value' => [150, 200],
        'compare' => 'BETWEEN',
        'type' => 'NUMERIC'
    ]
]);

// On récupère en général les résultats avec get_posts
$results = $query->get_posts();

Pour chacun des exemples, on récupérerait en général les résultats avec get_posts, mais les autres méthodes have_post et the_post que l’on utilise souvent comme fonction globale sont en réalité des méthodes de WP_Query que l’on peut utiliser sur toute instance afin de travailler avec les résultats.

Query dans une query

Lorsque l’on effectue une query alors qu’une autre est en cours (par exemple dans la loop), notre custom query va perturber la query globale.

Afin de ne pas avoir de bug, dès que l’on a terminé le traitement de notre query custom, on restore l’objet query principal. On utilise pour cela wp_reset_postdata.

Bien entendu, dans la mesure où la custom query est exécuté en dehors de toute autre requête, il n’y a rien à faire.

Je pense que vous commencez à comprendre la constructions des requêtes avancées et les possibilités qu’elles ouvrent. C’est aussi ces fonctions qui font la puissance de WordPress en tant que framework.

Filtres produits sur le site de Baumer
Chez Baumer, il est possible de filtrer par matières

Le cas WooCommerce

Même WooCommerce utilise WP_Query sous le capot. Cependant, il est recommandé d’utiliser wc_get_products afin de se prémunir de futurs changements dans la façon dont les données sont stockées.

Pour les requêtes sur les attributs, le nom de l’attribut est toujours préfixé par pa_ (product attribute). De ce fait, le nom de l’attribut color est pa_color.

// tax query pour les matières (material)
'tax_query' => [[
    'taxonomy' => 'pa_material',
    'field' => 'term_id',
    'terms' => […],
    'operator' => 'IN'
]]

Par ailleurs, par défaut, wc_get_products ne supporte pas les requêtes sur les métas qu’offre WP_Query. Il suffit de quelques lignes pour en ajouter le support.

add_filter('woocommerce_product_data_store_cpt_get_products_query', function ($query, $query_vars) {
    if (!empty($query_vars['meta_query'])) {
        $query['meta_query'] = $query_vars['meta_query'];
    }

    return $query;
}, 10, 2);

En plus de wc_get_products, WooCommerce fournit deux autres fonctions très utiles : wc_get_orders et WC_Order_Query, dont vous trouverez la documentation sur GitHub.

Requêtes SQL avec wpdb

Nous l’avons constaté, il est possible de créer des requêtes vraiment très puissante avec l’ORM de WP. Cependant, il arrive parfois que l’on ait besoin d’un outil plus bas niveau pour, par exemple, gérer des tables custom ou optimiser des requêtes complexes. C’est là que wpdb entre en scène.

Dans sa forme la plus simple, wpdb permet d’executer une requête SQL et d’en récupérer les résultats.

global $wpdb;
$results = $wpdb->get_results("SELECT * FROM 'wp_options'");

Par défaut, les résultats sont sous forme d’objet, mais il est possible comme souvent d’obtenir un tableau associatif ou indexé. Il suffit pour cela de passer une des trois constantes en second argument : OBJECT, ARRAY_A, ARRAY_N.

À ce stade, vous avez peut-être déjà remarqué que mettre en dur le nom des tables, sachant qu’elles sont préfixées de manière unique à chaque site, risque de poser des problèmes : de portabilité notamment. Évidemment, tout est prévu car wpdb met à notre disposition une propriété par table afin d’automatiquement en récupérer le nom.

global $wpdb;
$results = $wpdb->get_results("SELECT * FROM {$wpdb->options}");

Évidemment, cela va légèrement se compliquer dès lors que l’on travaille avec des tables non standard. Cependant, $wpdb met également à notre disposition la propriété $base_prefix. De ce fait, si l’on souhaite mentionner une table appelée my_custom_table on peut la référencer sans se soucier du préfixe en utilisant $base_prefix.

global $wpdb;
$results = $wpdb->get_results("SELECT * FROM {$wpdb->base_prefix}my_custom_table");

Nous avons ici seulement mentionné la méthode get_results, mais il en existe trois autres permettant de récupérer des données :

À vous de voir laquelle est la plus adaptée à votre besoin. wpdb propose des méthodes pour les opérations les plus courantes (insert, update, delete) ainsi que des propriétés permettant d’accéder aux informations utiles. Nous avons vu $base_prefix, mais on peut avoir besoin du $insert_id par exemple, ou de consulter $last_error.

En dernier lieu, nous pouvons utiliser la méthode générique query afin d’exécuter n’importe quelle requête. Cette méthode est la plus bas niveau offerte par wpdb et s’avère utile lorsqu’une requête très spécifique ou complexe n’est pas réalisable avec les autres méthodes (créer ou effacer une table…).

Sécuriser ses requêtes

Avant d’aborder tout autre sujet, parlons rapidement de la sécurité. Dans la mesure où nous écrivons ici directement des requêtes MySQL, nos requêtes sont exécutées telles-quelles. Et si des données non gérées par nous y trouvent leur chemin, nous sommes en présence d’une faille de sécurité : l’injection SQL.

Comme en PHP avec PDO, nous avons une méthode prepare. Celle de WordPress, pour des raisons historiques, fonctionne cependant différemment, elle se comporte comme sprintf.

Nous avons trois placeholders selon le type de valeur :

$escaped_sql = $wpdb->prepare("SELECT id, name FROM {$wpdb->users} WHERE user_email = %s", $email);

// Il est également possible d'utiliser une syntaxe à la vsprintf
$escaped_sql = $wpdb->prepare("SELECT id, name FROM {$wpdb->users} WHERE user_email = %s", [$email]);

// On exécute ensuite normalement la requête préparée
$results = $wpdb->get_results($escaped_sql);

Les deux méthodes acceptent bien entendu optionnellement plusieurs paramètres : séparés par des virgules pour la première, tableau pour la seconde.

Il n’est pas nécessaire d’utiliser prepare avec toutes les valeurs. $wpdb->insert et $wpdb->update par exemple se chargent d’échapper les valeurs. Attention, les conditions ne sont pas échappées !

La documentation de chacune d’elle mentionne si une valeur doit être échappée ou non.

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```