Prepared statements are the single most important database security practice: they separate SQL structure from data, making SQL injection structurally impossible for the values you bind.
// INSERT
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
$stmt->execute(['name' => 'Alice', 'email' => 'alice@example.com']);
$newId = $pdo->lastInsertId();
// SELECT with user input — safe from SQL injection
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// UPDATE
$stmt = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
$stmt->execute(['name' => 'Alice Smith', 'id' => 1]);
// DELETE
$stmt = $pdo->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([1]);
Never do this:
// DANGEROUS — vulnerable to SQL injection, never concatenate user input
$pdo->query("SELECT * FROM users WHERE email = '$email'");
Transactions group multiple statements so they succeed or fail together:
$pdo->beginTransaction();
try {
$pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')->execute([100, 1]);
$pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')->execute([100, 2]);
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
Always use bound parameters (either named :name or positional ?) for any value coming from user input, query strings, or external sources — no exceptions.