Le problème : avant de lire, il faut construire (sans laisser la porte ouverte)
Jusqu'ici, les tables existaient déjà. Mais c'est vous qui les créez. Et le jour où votre site connecte ces tables à un formulaire, une faille béante peut s'ouvrir : un visiteur mal intentionné peut, via une simple zone de texte, lire ou détruire toute votre base. Ça s'appelle l'injection SQL, et c'est encore aujourd'hui l'une des failles les plus exploitées du web.
Cette leçon couvre les deux faces : concevoir une table propre, puis la sécuriser quand des données utilisateur entrent en jeu.
CREATE TABLE : types, clé primaire, clé étrangère
On définit une table en listant ses colonnes, chacune avec un type :
CREATE TABLE clients (
id INT PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL UNIQUE,
ville VARCHAR(80),
inscrit_le DATE DEFAULT (CURRENT_DATE)
);
Les types les plus courants : INT (entier), DECIMAL(8,2) (nombre à décimales, idéal pour un prix), VARCHAR(n) (texte court), TEXT (texte long), DATE et DATETIME. Les contraintes NOT NULL, UNIQUE et DEFAULT protègent la cohérence des données dès l'écriture.
Une table reliée déclare sa clé étrangère :
CREATE TABLE commandes (
id INT PRIMARY KEY AUTO_INCREMENT,
client_id INT NOT NULL,
montant DECIMAL(8,2) NOT NULL,
FOREIGN KEY (client_id) REFERENCES clients(id)
);
client_id pointe vers la clé primaire id de clients : chaque commande est reliée à un client existant.Un index accélère les recherches sur une colonne, comme l'index d'un livre. CREATE INDEX idx_ville ON clients(ville); rend les WHERE ville = ... beaucoup plus rapides sur de gros volumes. À utiliser sur les colonnes souvent filtrées ou jointes.
L'injection SQL : la faille qui vide votre base
Imaginez ce code PHP qui cherche un client par email, en collant directement la saisie de l'utilisateur dans la requête :
// CODE VULNÉRABLE — NE JAMAIS FAIRE ÇA
$email = $_POST['email'];
$sql = "SELECT * FROM clients WHERE email = '$email'";
$resultat = $pdo->query($sql);
Si l'utilisateur tape un email normal, ça marche. Mais s'il saisit :
' OR '1'='1
La requête envoyée devient :
SELECT * FROM clients WHERE email = '' OR '1'='1'
'1'='1' est toujours vrai : la requête renvoie tous les clients. Avec une saisie plus agressive ('; DROP TABLE clients; --), l'attaquant peut carrément supprimer la table.
Ne concaténez JAMAIS une saisie utilisateur dans une requête SQL. Tout ce qui vient de l'extérieur (formulaire, URL, en-tête HTTP) est hostile par défaut. Coller cette donnée dans une chaîne SQL, c'est laisser l'utilisateur réécrire votre requête.
Le remède : les requêtes préparées
La solution s'appelle requête préparée (prepared statement). Le principe : on envoie d'abord la structure de la requête avec des placeholders, puis les valeurs séparément. La base traite alors la saisie comme une simple donnée, jamais comme du code SQL.
// CODE SÉCURISÉ — requête préparée (PDO)
$email = $_POST['email'];
$stmt = $pdo->prepare("SELECT * FROM clients WHERE email = :email");
$stmt->execute(['email' => $email]);
$resultat = $stmt->fetchAll();
Ici, :email est un placeholder. Même si l'utilisateur tape ' OR '1'='1, cette chaîne est cherchée telle quelle comme valeur d'email — elle ne devient jamais du code. La faille disparaît.
Toujours utiliser des requêtes préparées dès qu'une donnée externe entre dans une requête. C'est la règle de sécurité numéro un en SQL. Les placeholders (:nom avec PDO, ou ? positionnels) séparent le code des données, ce qui rend l'injection impossible.
Lien avec l'IA : l'IA produit souvent, par défaut, du code qui concatène les variables dans la requête — pile la faille ci-dessus. Ne lui faites pas une confiance aveugle : exigez explicitement des requêtes préparées dans votre prompt, et relisez chaque requête générée pour vérifier qu'aucune variable n'est collée directement dans la chaîne SQL.
The problem: before reading, you must build (without leaving the door open)
So far, the tables already existed. But you are the one creating them. And the day your site connects these tables to a form, a gaping hole can open: a malicious visitor can, through a simple text field, read or destroy your entire database. It is called SQL injection, and it is still today one of the most exploited web vulnerabilities.
This lesson covers both sides: designing a clean table, then securing it when user data comes into play.
CREATE TABLE: types, primary key, foreign key
You define a table by listing its columns, each with a type:
CREATE TABLE clients (
id INT PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL UNIQUE,
ville VARCHAR(80),
inscrit_le DATE DEFAULT (CURRENT_DATE)
);
The most common types: INT (integer), DECIMAL(8,2) (decimal number, ideal for a price), VARCHAR(n) (short text), TEXT (long text), DATE and DATETIME. The constraints NOT NULL, UNIQUE and DEFAULT protect data consistency from the moment of writing.
A related table declares its foreign key:
CREATE TABLE commandes (
id INT PRIMARY KEY AUTO_INCREMENT,
client_id INT NOT NULL,
montant DECIMAL(8,2) NOT NULL,
FOREIGN KEY (client_id) REFERENCES clients(id)
);
client_id points to the primary key id in clients: each order links to an existing client.An index speeds up searches on a column, like a book's index. CREATE INDEX idx_ville ON clients(ville); makes WHERE ville = ... much faster on large volumes. Use it on columns often filtered or joined.
SQL injection: the flaw that empties your database
Imagine this PHP code searching for a customer by email, pasting the user input directly into the query:
// VULNERABLE CODE - NEVER DO THIS
$email = $_POST['email'];
$sql = "SELECT * FROM clients WHERE email = '$email'";
$resultat = $pdo->query($sql);
If the user types a normal email, it works. But if they enter:
' OR '1'='1
The sent query becomes:
SELECT * FROM clients WHERE email = '' OR '1'='1'
'1'='1' is always true: the query returns all customers. With a more aggressive input ('; DROP TABLE clients; --), the attacker can simply delete the table.
NEVER concatenate user input into a SQL query. Anything from the outside (form, URL, HTTP header) is hostile by default. Pasting that data into a SQL string lets the user rewrite your query.
The cure: prepared statements
The solution is the prepared statement. The principle: you first send the query structure with placeholders, then the values separately. The database then treats the input as plain data, never as SQL code.
// SECURE CODE - prepared statement (PDO)
$email = $_POST['email'];
$stmt = $pdo->prepare("SELECT * FROM clients WHERE email = :email");
$stmt->execute(['email' => $email]);
$resultat = $stmt->fetchAll();
Here, :email is a placeholder. Even if the user types ' OR '1'='1, this string is searched as-is as an email value — it never becomes code. The flaw disappears.
Always use prepared statements as soon as external data enters a query. This is the number-one SQL security rule. Placeholders (:name with PDO, or positional ?) separate code from data, which makes injection impossible.
Link with AI: AI often produces, by default, code that concatenates variables into the query — exactly the flaw above. Do not trust it blindly: explicitly demand prepared statements in your prompt, and review every generated query to check that no variable is pasted directly into the SQL string.
À vous d'essayer — un schéma propre avec clé primaire et clé étrangère :
CREATE TABLE clients (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE commandes (
id INTEGER PRIMARY KEY,
client_id INTEGER NOT NULL,
montant REAL NOT NULL,
FOREIGN KEY (client_id) REFERENCES clients(id)
);
INSERT INTO clients VALUES (1, 'Alice Durand', 'alice@exemple.fr');
INSERT INTO commandes VALUES (1, 1, 120.50);
SELECT clients.nom, commandes.montant
FROM clients
JOIN commandes ON commandes.client_id = clients.id;
Faites auditer un code par l'IA pour repérer l'injection SQL, et exigez une version en requête préparée :
query($sql);. Question 1 : ce code est-il vulnérable à l'injection SQL ? Explique précisément l'attaque possible. Question 2 : réécris-le avec une requête préparée PDO utilisant un placeholder, sans jamais concaténer la saisie. Commente chaque ligne." data-i18n-en="You are a web security expert. Here is some PHP code: $email = $_POST['email']; $sql = \"SELECT * FROM clients WHERE email = '$email'\"; $pdo->query($sql);. Question 1: is this code vulnerable to SQL injection? Explain the possible attack precisely. Question 2: rewrite it with a PDO prepared statement using a placeholder, never concatenating the input. Comment each line.">Tu es un expert en sécurité web. Voici un bout de code PHP : $email = $_POST['email']; $sql = "SELECT * FROM clients WHERE email = '$email'"; $pdo->query($sql);. Question 1 : ce code est-il vulnérable à l'injection SQL ? Explique précisément l'attaque possible. Question 2 : réécris-le avec une requête préparée PDO utilisant un placeholder, sans jamais concaténer la saisie. Commente chaque ligne.
Sans relire la réponse de l'IA : avec tes mots, pourquoi une requête préparée empêche l'injection alors qu'une requête concaténée ne le fait pas ?
' OR '1'='1 devient du code exécuté. Avec une requête préparée, on envoie d'abord la structure avec un placeholder (:email), puis la valeur séparément : la base traite la saisie comme une simple donnée, jamais comme du SQL. Code et données sont séparés, donc l'injection est impossible.Réécrivez le code vulnérable en requête préparée PDO. Votre code doit appeler prepare() avec un placeholder (:email ou ?) et execute() avec la valeur séparée. Aucune concaténation de $email dans la chaîne SQL.
Tu as demandé à l'IA une fonction PHP qui recherche un client par son nom. Ton rôle de relecteur : l'accepter telle quelle ou la rejeter, et dire pourquoi.
function chercherClient($pdo, $nom) {
$sql = "SELECT * FROM clients WHERE nom = '" . $nom . "'";
return $pdo->query($sql)->fetchAll();
}
$nom vient de l'utilisateur et il est concaténé directement dans la chaîne SQL : c'est exactement l'injection vue dans la leçon. Une saisie comme ' OR '1'='1 renvoie tous les clients, et '; DROP TABLE clients; -- peut détruire la table. Le fait que la fonction soit bien nommée et lisible ne change rien : il faut une requête préparée avec prepare() + execute() et un placeholder (:nom).Sans remonter dans la leçon : qu'est-ce qu'une injection SQL, et quelle est la parade en une phrase ?
' OR '1'='1 qui renvoie toute la table). La parade : la requête préparée, qui envoie la structure avec un placeholder (:email) puis la valeur séparément, si bien que la saisie reste une donnée et ne devient jamais du SQL.Vous savez désormais concevoir et interroger une base solide. Pour franchir un cap, il faut changer de façon de penser : organiser le code autour d'objets plutôt que d'instructions. Le cours sur la POO vous apprend à raisonner ainsi, tranquillement.
La POO expliquee simplement →