How do I prevent SQL injection in PHP?
Short answer
Use prepared statements with bound parameters. The query structure reaches the database before the values do, so a value can never be reinterpreted as SQL.
#Never build a query by concatenation
// CATASTROPHIC
$sql = "SELECT * FROM users WHERE email = '$email'";Input of ' OR '1'='1 returns every row. Input of '; DROP TABLE users; -- does what it says.
#Do this instead
$pdo = new PDO('mysql:host=localhost;dbname=app;charset=utf8mb4', $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare('SELECT id, username FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();ATTR_EMULATE_PREPARES => false matters — it makes PDO use the database's real prepared statements instead of doing string substitution in PHP.
#Named placeholders read better for several values
$stmt = $pdo->prepare(
'INSERT INTO videos (title, track, views) VALUES (:title, :track, :views)'
);
$stmt->execute([
'title' => $title,
'track' => $track,
'views' => $views,
]);#Placeholders bind values, never identifiers
You cannot parameterise a table or column name. If a sort column comes from user input, check it against an allowlist:
$allowed = ['title', 'views', 'duration'];
$column = in_array($_GET['sort'] ?? '', $allowed, true) ? $_GET['sort'] : 'title';
$stmt = $pdo->query("SELECT * FROM videos ORDER BY $column");The true third argument to in_array forces strict comparison — without it, PHP's loose typing can make surprising values match.
#What about IN clauses?
Build the placeholders to match the count:
$ids = [1, 2, 3];
$marks = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM videos WHERE id IN ($marks)");
$stmt->execute($ids);