Chapitre 5.6 — Base de données : $wpdb
⏱️ TL;DR —
$wpdbest l’objet global d’accès à la base de données. On l’utilise seulement quand les API de haut niveau (WP_Query,get_post_meta,get_option…) ne suffisent pas. Ses méthodes clés :get_results,get_row,get_var,get_col,query,insert,update,delete. Règle absolue de sécurité : toute requête avec une variable passe par$wpdb->prepare()(requêtes préparées) — jamais de concaténation, sous peine d’injection SQL. Et on utilise$wpdb->prefix/$wpdb->posts, jamais un nom de table en dur.
🎯 Objectifs
- Savoir quand descendre au niveau
$wpdb(et quand ne pas le faire). - Utiliser les méthodes de lecture et d’écriture.
- Écrire des requêtes préparées et sûres.
- Référencer les tables sans coder le préfixe en dur.
1. Quand (et quand ne pas) utiliser $wpdb
Préférez toujours les API de haut niveau : WP_Query/get_posts (contenus), get_post_meta/update_post_meta (métadonnées), get_option/update_option (réglages), get_terms (taxonomies). Elles gèrent le préfixe, le cache, la sécurité et la compatibilité.
Descendez à $wpdb uniquement pour :
- des requêtes agrégées/complexes que les API ne savent pas exprimer (rapports,
GROUP BY, jointures spécifiques) ; - vos propres tables custom (un plugin qui crée sa table, Partie 7) ;
- des opérations de masse performantes.
⚠️ Piège — Réécrire en SQL brut ce que
WP_Queryfait déjà, c’est perdre le cache, la sécurité et la compatibilité (et souvent introduire des bugs).$wpdbest un dernier recours, pas le réflexe par défaut.
2. L’objet et les noms de tables
global $wpdb;
// Tables du cœur (propriétés) — jamais de nom en dur
$wpdb->posts; // wp_posts
$wpdb->postmeta; // wp_postmeta
$wpdb->options; // wp_options
$wpdb->users; // wp_users
$wpdb->prefix; // "wp_" (ou le préfixe réel du site)
// Une table custom de votre plugin
$table = $wpdb->prefix . 'mon_plugin_logs';⚠️ Piège — le préfixe en dur. Écrire
"wp_posts"casse sur un site au préfixe différent (wp_a1b2_). Utilisez toujours$wpdb->postsou$wpdb->prefix.
3. Lire : get_results / get_row / get_var / get_col
// Plusieurs lignes (tableau d'objets)
$rows = $wpdb->get_results( "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_status='publish'" );
// Une ligne (objet)
$row = $wpdb->get_row( "SELECT * FROM {$wpdb->posts} WHERE ID = 42" );
// Une seule valeur
$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts}" );
// Une colonne (tableau de valeurs)
$ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} LIMIT 10" );4. La règle d’or : prepare() (requêtes préparées)
Dès qu’une variable entre dans une requête, elle passe par $wpdb->prepare(). C’est ce qui empêche l’injection SQL.
// ✅ SÛR : requête préparée
$id = 42;
$statut = 'publish';
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d AND post_status = %s",
$id,
$statut
)
);
// ❌ DANGEREUX : concaténation → injection SQL possible
$row = $wpdb->get_row( "SELECT * FROM {$wpdb->posts} WHERE ID = " . $_GET['id'] );Placeholders : %d (entier), %s (chaîne), %f (flottant). Le nom de table et les mots-clés SQL ne se « préparent » pas (ils ne viennent jamais d’une entrée utilisateur) — mais toutes les valeurs oui.
⚠️ Piège — « je fais juste un
intval, ça suffit ». Caster en entier une valeur peut suffire ponctuellement, mais la règle systématique et sans exception estprepare()pour toute valeur : c’est plus sûr, plus lisible, et ça résiste aux refactors. L’injection SQL via$wpdbnon préparé est une faille classique des plugins (Partie 11).
5. Écrire : insert / update / delete (échappement automatique)
Pour les écritures simples, $wpdb->insert/update/delete échappent automatiquement les valeurs (pas besoin de prepare()) :
// INSERT
$wpdb->insert(
$wpdb->prefix . 'mon_plugin_logs',
array( 'user_id' => 1, 'message' => 'Bonjour', 'created' => current_time( 'mysql' ) ),
array( '%d', '%s', '%s' ) // formats
);
$nouvel_id = $wpdb->insert_id;
// UPDATE
$wpdb->update(
$wpdb->prefix . 'mon_plugin_logs',
array( 'message' => 'Modifié' ), // data
array( 'id' => $nouvel_id ), // where
array( '%s' ), // format data
array( '%d' ) // format where
);
// DELETE
$wpdb->delete( $wpdb->prefix . 'mon_plugin_logs', array( 'id' => $nouvel_id ), array( '%d' ) );Pour du SQL arbitraire (DDL, requêtes complexes) : $wpdb->query( $sql ) — préparé si des variables entrent en jeu.
💡 Pour un dev React —
$wpdbest un client SQL bas niveau (commepg/mysql2en Node) : puissant mais sans garde-fou automatique.prepare()= les requêtes paramétrées ($1, $2) que vous connaissez ; la concaténation = la faille que votre linter vous interdit. Les méthodesinsert/update/deletesont un mini query-builder qui échappe pour vous. Même hygiène que côté Node : jamais d’interpolation de chaîne dans du SQL.
6. Créer sa propre table (aperçu)
Un plugin qui a besoin d’un schéma dédié (logs, relations complexes que le modèle post/meta rend inefficace) crée sa table à l’activation, via dbDelta() (qui gère créations/migrations) :
function monplugin_creer_table() {
global $wpdb;
$table = $wpdb->prefix . 'mon_plugin_logs';
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
message TEXT NOT NULL,
created DATETIME NOT NULL,
PRIMARY KEY (id),
KEY user_id (user_id)
) $charset;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql ); // crée ou met à jour la table
}
// à brancher sur register_activation_hook (Partie 7)On approfondit l’activation et la création de tables en Partie 7.
✏️ Exercices
- Corrigez cette requête dangereuse :
$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_author_email = '".$_POST['email']."'"). - Pourquoi ne jamais écrire
"wp_options"en dur dans une requête ? - Quand faut-il descendre au niveau
$wpdbplutôt qu’utiliserWP_Query/get_option?
✅ Solution
$email = sanitize_email( $_POST['email'] );
$count = $wpdb->get_var(
$wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_author_email = %s", $email )
);On prépare la valeur (%s), on ne concatène jamais une entrée utilisateur.
2. Parce que le préfixe de tables peut différer selon le site (wp_a1b2_) ; il faut $wpdb->options/$wpdb->prefix pour rester portable.
3. Seulement quand les API de haut niveau ne suffisent pas : requêtes agrégées/complexes, tables custom d’un plugin, opérations de masse. Sinon, on garde WP_Query/get_* (cache + sécurité + compat).
🧠 Quiz de révision
1. Qu’est-ce que $wpdb ?
L’objet global d’accès à la base de données, utilisé quand les API de haut niveau ne suffisent pas.
2. Quelle est la règle absolue avec les variables dans une requête ?
Les passer par $wpdb->prepare() (requêtes préparées, placeholders %d/%s/%f) — jamais de concaténation.
3. Comment référencer la table des articles ?
Via $wpdb->posts (ou $wpdb->prefix . 'posts'), jamais "wp_posts" en dur.
4. Les méthodes insert/update/delete exigent-elles prepare() ?
Non : elles échappent automatiquement les valeurs (via les formats fournis). prepare() est requis pour le SQL brut de query()/get_*.
5. Comment un plugin crée-t-il sa propre table ?
Avec dbDelta() (à l’activation), qui crée ou met à jour la table selon le SQL fourni.
Chapitre suivant : Options, transients & cache objet.