How to Prevent SQL Injection in PHP with Prepared Statements?

How to Prevent SQL Injection in PHP with Prepared Statements?

If you’re building a PHP application that interacts with a database, one of the most important things you need to take seriously is SQL injection. It’s a common security threat where an attacker can trick your app into running malicious SQL commands by injecting them through user input , like a login form or search bar.
Luckily, there’s a reliable way to protect your app: use prepared statements with bound parameters. These are built right into PHP and are very easy to use with either mysqli or PDO.
How to Prevent SQL Injection in PHP with Prepared Statements?

The Problem with Raw SQL Queries

Let’s say you have a login form and you write something like this:

$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);

This might seem fine at first. But if someone enters ‘ OR 1=1 — as the username, it turns your query into:

SELECT * FROM users WHERE username = '' OR 1=1 --' AND password = ''

Now suddenly, the condition always returns true , and boom, the attacker gets in without a password. That’s how SQL injection works.

The Right Way: Using Prepared Statements

Prepared statements work by separating your SQL logic from the actual data. You write the SQL with placeholders, and then you bind user input to those placeholders. This makes sure user input is treated strictly as data, not as part of the SQL code.

Using mysqli:

$conn = new mysqli("localhost", "root", "", "mydb");
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$username = $_POST['username'];
$password = $_POST['password'];
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
    echo "Login successful!";
} else {
    echo "Invalid credentials.";
}
$stmt->close();
$conn->close();

ss stands for “string, string” – meaning both inputs are strings.

Using PDO (another PHP database library):

$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "");
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$username = $_POST['username'];
$password = $_POST['password'];
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$stmt->execute();
if ($stmt->rowCount() > 0) {
    echo "Login successful!";
} else {
    echo "Wrong username or password.";
}

With PDO, the syntax is a bit cleaner, and it also works with many types of databases , not just MySQL.

A Few Extra Tips

  • Never insert user input directly into your SQL , not even if you think it’s “safe.”
  • Hash your passwords with password_hash() and verify with password_verify() , don’t store them in plain text!
  • Don’t show detailed errors to users. If something breaks, log it internally but keep your error messages vague on the frontend.

This code provides secure database operations by preventing SQL injection through proper parameter binding.

<?php
// MySQLi Prepared Statement Example
// Database connection
$mysqli = new mysqli("localhost", "username", "password", "database");
if ($mysqli->connect_error) {
    die("Connection failed: " . $mysqli->connect_error);
}
// Example 1: SELECT with prepared statement
function getUserById($mysqli, $userId) {
    $stmt = $mysqli->prepare("SELECT id, username, email FROM users WHERE id = ?");
    $stmt->bind_param("i", $userId); // 'i' for integer
    $stmt->execute();
    $result = $stmt->get_result();
    $user = $result->fetch_assoc();
    $stmt->close();
    return $user;
}
// Example 2: INSERT with prepared statement
function addUser($mysqli, $username, $email) {
    $stmt = $mysqli->prepare("INSERT INTO users (username, email) VALUES (?, ?)");
    $stmt->bind_param("ss", $username, $email); // 's' for string
    $success = $stmt->execute();
    $stmt->close();
    return $success;
}
// PDO Prepared Statement Example
try {
    $pdo = new PDO("mysql:host=localhost;dbname=database", "username", "password");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Example 3: SELECT with named parameters
    function getUserByEmail($pdo, $email) {
        $stmt = $pdo->prepare("SELECT id, username, email FROM users WHERE email = :email");
        $stmt->execute(['email' => $email]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
// Example 4: INSERT with named parameters
    function addUserPdo($pdo, $username, $email) {
        $stmt = $pdo->prepare("INSERT INTO users (username, email) VALUES (:username, :email)");
        return $stmt->execute([
            'username' => $username,
            'email' => $email
        ]);
    }

} catch(PDOException $e) {
    die("Connection failed: " . $e->getMessage());
}
// Usage examples
$userId = 1; // From user input
$username = "john_doe"; // From user input
$email = "[email protected]"; // From user input
// MySQLi usage
$user = getUserById($mysqli, $userId);
$insertSuccess = addUser($mysqli, $username, $email);
// PDO usage
$userPdo = getUserByEmail($pdo, $email);
$insertSuccessPdo = addUserPdo($pdo, $username, $email);
?>

Final Thoughts

Prepared statements are one of the easiest and most effective ways to stop SQL injection in its tracks. Once you get into the habit of using them, you’ll wonder why anyone ever did it the old (and dangerous) way.
If you’re already working with raw queries, now’s the time to refactor. It’s a quick change that could save you from serious headaches down the road.