Le WordPress d'aujourd'hui, décodé pour les développeurs

Headless & API

« Requête $wpdb directe ou WP_Query pour une route REST maison : quand déroger »

Comparer les deux approches pour un endpoint de reporting agrégé, avec les risques de sécurité d'un SQL direct mal préparé face à la sécurité native de WP_Query.

Par Clément Hadrot • 5 février 2026 • 4 min de lecture • Aucun commentaire
"Requête $wpdb directe ou WP_Query pour une route REST maison : quand déroger"

« WordPress database error: You have an error in your SQL syntax » — ce message, visible dans les journaux d’erreur d’un serveur, est souvent le premier signe qu’une requête $wpdb construite à la main a été mal préparée, parfois avec une faille d’injection SQL en prime. Pour une route REST personnalisée, le choix entre WP_Query et une requête $wpdb directe engage donc bien plus qu’une question de performance : il engage la sécurité même de l’endpoint.

WP_Query : la voie sûre par défaut

WP_Query échappe automatiquement les valeurs qu’on lui transmet via ses paramètres structurés (meta_query, tax_query, post__in, etc.), et respecte nativement le système de capacités de WordPress pour filtrer les contenus visibles selon le rôle de l’utilisateur courant. Pour la quasi-totalité des routes REST qui listent, filtrent ou trient des contenus déjà représentés dans wp_posts, elle reste le choix par défaut le plus sûr et le plus maintenable.

$requete = new WP_Query( array(
    'post_type'      => 'produit',
    'meta_query'     => array(
        array(
            'key'     => 'stock_disponible',
            'value'   => 0,
            'compare' => '>',
            'type'    => 'NUMERIC',
        ),
    ),
    'posts_per_page' => 20,
) );

Là où WP_Query montre ses limites

Un endpoint de reporting agrégé — par exemple le chiffre d’affaires total par catégorie de produit sur les trente derniers jours — dépasse ce que WP_Query peut exprimer nativement. Les agrégations SQL (SUM(), GROUP BY, jointures entre plusieurs tables meta) n’ont pas d’équivalent direct dans l’API de WP_Query, qui reste conçue pour récupérer des objets WP_Post complets, pas des résultats statistiques agrégés.

L'essentiel à retenir : WP_Query protège par défaut, $wpdb non ; Les agrégations complexes justifient parfois $wpdb ; prepare() reste obligatoire sans exception

Comparatif sur un cas concret de reporting

CritèreWP_Query$wpdb direct
Échappement automatiqueOui, natifNon, à la charge du développeur
Respect des capacités utilisateurOui, via les filtres cœurNon, à réimplémenter manuellement
Agrégations SQL (SUM, GROUP BY)Impossible nativementPossible directement
Performance sur gros volumesCorrecte, avec des index adaptésSouvent meilleure, requête ciblée
Lisibilité et maintenanceÉlevée, code déclaratifPlus faible, SQL brut à documenter

Utiliser $wpdb sans ouvrir de faille

Lorsque l’agrégation impose de déroger à WP_Query, la méthode prepare() de la classe $wpdb n’est jamais optionnelle. Elle échappe correctement les valeurs insérées dans la requête, à condition d’utiliser des espaces réservés (%d, %s, %f) plutôt que de concaténer directement des variables dans la chaîne SQL :

global $wpdb;

$jours = 30;

$resultats = $wpdb->get_results( $wpdb->prepare(
    "SELECT t.name AS categorie, SUM(pm.meta_value) AS total
     FROM {$wpdb->posts} p
     JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = 'montant_commande'
     JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
     JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
     JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
     WHERE p.post_date >= DATE_SUB(NOW(), INTERVAL %d DAY)
     GROUP BY t.name",
    $jours
) );

Chaque valeur variable de la requête, ici le nombre de jours, passe par un espace réservé %d, jamais par une concaténation directe de chaîne. C’est cette discipline, et non le choix de $wpdb en lui-même, qui détermine si la route reste sûre.

Ne pas oublier le contrôle d’accès

Contrairement à WP_Query, une requête $wpdb directe ne filtre jamais automatiquement selon les capacités de l’utilisateur courant. Pour un endpoint de reporting, cela signifie ajouter explicitement une vérification de capacité dans le permission_callback de la route :

'permission_callback' => function() {
    return current_user_can( 'manage_woocommerce' );
},

Verdict argumenté

WP_Query reste le choix par défaut pour toute route qui liste ou filtre des contenus déjà structurés comme des articles WordPress : elle protège nativement contre l’injection et respecte les capacités utilisateur sans effort supplémentaire. Déroger vers une requête $wpdb directe se justifie uniquement pour des agrégations que WP_Query ne sait pas exprimer, et implique alors une responsabilité accrue : préparation systématique des requêtes et vérification manuelle des permissions, deux disciplines que WP_Query offrait gratuitement.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi