iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

MySQL Prepared Statements

Prepared statements separate the SQL from the data. The database parses the SQL once and binds your values safely — no string concatenation, no injection.

Why prepared

Concat (DON'T)Prepared (DO)
"SELECT * FROM users WHERE email = '$email'"$pdo->prepare('SELECT * FROM users WHERE email = ?')->execute([$email])
Injection-prone.Driver handles escaping.
Parsed each call.Parsed once, executed many.

Positional placeholders

PHP
$stmt = $pdo->prepare('SELECT id, name FROM customers WHERE country = ?');
$stmt->execute(['AU']);
print_r($stmt->fetchAll());

Named placeholders

PHP
$stmt = $pdo->prepare(
    'SELECT * FROM customers WHERE country = :c AND active = :a'
);
$stmt->execute([':c' => 'AU', ':a' => 1]);

bindValue vs bindParam vs execute(array)

MethodNotes
execute([$v1, $v2])Shortest; binds everything as string.
bindValue(1, $v, PDO::PARAM_INT)Binds a value with explicit type.
bindParam(1, $v, PDO::PARAM_INT)Binds a variable by reference — value is read at execute() time.

When the type matters

For LIMIT / OFFSET placeholders, MySQL needs an integer. With EMULATE_PREPARES = false, you must bind explicitly:

PHP
$stmt = $pdo->prepare('SELECT * FROM customers LIMIT ?');
$stmt->bindValue(1, 20, PDO::PARAM_INT);
$stmt->execute();

What you CAN'T bind

  • Table or column names — only values.
  • ORDER BY direction — that's part of the SQL grammar.
  • LIMIT inside an IN list — but you can build the placeholder string dynamically.

For these, use an allow-list — never accept user input directly into the SQL string.

Tip: Always set PDO::ATTR_EMULATE_PREPARES => false. It pushes type checking to the server and prevents a few edge-case injection vectors.

Example

Example
<?php
// ✗ injection risk
// $pdo->query("SELECT * FROM users WHERE email = '$email'");
// ✓ safe
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
Try it Yourself »

Exercise

Best emulation setting for security.

PDO::ATTR_EMULATE_PREPARES =>

Test yourself

Q1. Prepared statements protect against…
Q2. You CAN bind…
Q3. For LIMIT integer binding with EMULATE_PREPARES=false you should…

Discussion

Loading…