Skip to Content

Chapitre 5.6 — Base de données : $wpdb

⏱️ TL;DR$wpdb est 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_Query fait déjà, c’est perdre le cache, la sécurité et la compatibilité (et souvent introduire des bugs). $wpdb est 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->posts ou $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 est prepare() pour toute valeur : c’est plus sûr, plus lisible, et ça résiste aux refactors. L’injection SQL via $wpdb non 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$wpdb est un client SQL bas niveau (comme pg/mysql2 en 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éthodes insert/update/delete sont 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

  1. Corrigez cette requête dangereuse : $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_author_email = '".$_POST['email']."'").
  2. Pourquoi ne jamais écrire "wp_options" en dur dans une requête ?
  3. Quand faut-il descendre au niveau $wpdb plutôt qu’utiliser WP_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.