Question
If user input is inserted directly into an SQL query without being handled safely, the application becomes vulnerable to SQL injection.
For example:
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
In this case, a malicious user could submit input such as:
value'); DROP TABLE table;--
That could turn the query into something like:
INSERT INTO `table` (`column`) VALUES ('value'); DROP TABLE table;--')
How can this be prevented in PHP?
Short Answer
By the end of this page, you will understand what SQL injection is, why directly concatenating user input into SQL is dangerous, and how to prevent it in PHP using prepared statements, parameterized queries, and basic validation. You will also see common mistakes, real-world usage patterns, and a small practical project.
Concept
SQL injection happens when untrusted input is treated as part of an SQL command instead of as plain data.
In unsafe code, a query is built by joining strings:
$sql = "INSERT INTO users (name) VALUES ('" . $_POST['name'] . "')";
If the input contains SQL syntax such as quotes, comments, or extra commands, the database may interpret that input as executable SQL.
The core protection is prepared statements.
With prepared statements, you send the SQL structure separately from the data. The database knows which parts are commands and which parts are values. That means user input is handled as data only, not as SQL code.
Safe code looks like this:
$stmt = $pdo->prepare("INSERT INTO users (name) VALUES (?)");
$stmt->execute([$_POST['name']]);
Why this matters in real programming:
- It protects login forms, search forms, filters, and admin tools.
- It prevents attackers from reading, changing, or deleting data.
- It avoids fragile string-escaping logic.
- It is the standard secure way to interact with databases in PHP.
Important note: the old mysql_* functions are deprecated and removed. In modern PHP, use PDO or MySQLi.
Mental Model
Think of SQL as a form with blanks to fill in.
- The SQL query is the printed form.
- User input is the value written into the blanks.
If you build SQL by concatenating strings, you are letting the user write directly on the form, including in the instruction area. They can change the meaning of the whole form.
Prepared statements work like locked fields:
- The structure of the form is fixed first.
- The user can only provide values for specific blanks.
- Their input cannot rewrite the instructions.
So the rule is simple: users provide data, not SQL syntax.
Syntax and Examples
Safe syntax with PDO
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("INSERT INTO users (name) VALUES (:name)");
$stmt->execute([
':name' => $_POST['user_input']
]);
Why this is safe
:nameis a placeholder.- The SQL statement is prepared first.
- The value from
$_POST['user_input']is sent separately. - Even if the input contains quotes or SQL-like text, it is treated as data.
Safe syntax with MySQLi
$mysqli = new mysqli('localhost', 'username', 'password', 'test');
$stmt = ->();
->(, []);
->();
Step by Step Execution
Consider this PDO example:
$name = "value'); DROP TABLE users;--";
$stmt = $pdo->prepare("INSERT INTO users (name) VALUES (:name)");
$stmt->execute([':name' => $name]);
Step by step
-
$nameis assigned a suspicious-looking string. -
$pdo->prepare(...)sends the SQL structure to the database:INSERT INTO users (name) VALUES (:name) -
The database sees
:nameas a value placeholder, not as part of the SQL command. -
$stmt->execute([':name' => $name])sends the input separately. -
The database stores the entire text:
value'); DROP TABLE users;-- -
No extra SQL command is executed.
Compare with unsafe concatenation
Real World Use Cases
Prepared statements are used anywhere an application sends external input to a database.
Common examples
- Login forms: checking email and password records safely
- Registration forms: inserting usernames, emails, and profile data
- Search filters: querying products, users, or orders by user-provided terms
- Admin dashboards: updating records based on form submissions
- REST APIs: reading route parameters and request body values safely
- Import scripts: inserting CSV or JSON data into tables
Example: login lookup
$stmt = $pdo->prepare("SELECT id, password_hash FROM users WHERE email = :email");
$stmt->execute([':email' => $_POST['email']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
Example: product search
$stmt = $pdo->prepare("SELECT * FROM products WHERE category_id = :category_id");
$stmt->execute([':category_id' => $_GET[]]);
= ->(PDO::);
Real Codebase Usage
In real PHP projects, developers usually combine prepared statements with a few other habits.
1. Validation before querying
Check that the input matches expectations.
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false) {
throw new InvalidArgumentException('Invalid user id');
}
2. Guard clauses
Stop early when required input is missing or invalid.
$email = trim($_POST['email'] ?? '');
if ($email === '') {
exit('Email is required');
}
3. Centralized database access
Many projects create one PDO connection and reuse it through repository or service classes.
function findUserByEmail(PDO $pdo, string $email): | {
= ->();
->([ => ]);
->(PDO::);
}
Common Mistakes
1. Using string concatenation
Broken:
$sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'";
Why it is wrong:
- Raw input is inserted directly into the query.
- Quotes inside input can break the SQL structure.
Fix:
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute([':email' => $_POST['email']]);
2. Relying only on escaping
Broken idea:
$email = addslashes($_POST['email']);
$sql = "SELECT * FROM users WHERE email = '$email'";
Why it is wrong:
addslashes()is not a safe SQL injection defense.- Escaping rules depend on the database connection and encoding.
Comparisons
| Approach | Safe from SQL injection? | Recommended? | Notes |
|---|---|---|---|
| String concatenation | No | No | User input becomes part of SQL |
addslashes() | No | No | Not a reliable SQL defense |
mysqli_real_escape_string() | Better but not ideal | Usually no for new code | Safer than manual escaping, but prepared statements are better |
| Prepared statements with PDO | Yes | Yes | Clean, flexible, widely used |
| Prepared statements with MySQLi | Yes | Yes | Good option for MySQL specifically |
PDO vs MySQLi
Cheat Sheet
Safe rule
Never put raw user input directly into SQL strings.
Use PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute([':email' => $email]);
Use MySQLi
$stmt = $mysqli->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param('s', $email);
$stmt->execute();
Good practices
- Use prepared statements for all external input
- Use PDO or MySQLi, not
mysql_* - Validate input with
filter_input()or custom checks - Use whitelists for dynamic column names or sort directions
- Enable PDO exceptions for easier debugging
Important edge cases
- Placeholders work for values, not table names or column names
FAQ
Is addslashes() enough to prevent SQL injection?
No. It is not a reliable SQL injection defense. Use prepared statements instead.
Should I still validate input if I use prepared statements?
Yes. Prepared statements make queries safe, but validation checks whether the input is acceptable for your application.
Is PDO better than MySQLi?
Both support prepared statements and are safe when used correctly. PDO is more flexible because it supports multiple database systems.
Can prepared statements protect table names and column names?
No. Placeholders are for values only. For dynamic identifiers, use a whitelist.
Are old mysql_query() functions safe to use?
No. The mysql_* extension is obsolete and removed. Use PDO or MySQLi.
Do prepared statements stop all security problems?
No. They prevent SQL injection when used properly, but you still need validation, authentication, authorization, and proper error handling.
What about numeric values like IDs?
They should still be validated and bound as parameters. Do not trust request data just because it looks numeric.
Mini Project
Description
Build a small PHP form that saves a comment into a MySQL database safely. This demonstrates the most common real-world fix for SQL injection: using PDO prepared statements instead of string concatenation.
Goal
Create a comment submission script that accepts user input, validates it, and inserts it into the database without exposing the application to SQL injection.
Requirements
- Create a PDO database connection with error mode enabled
- Read a comment from a POST request
- Reject empty comments
- Insert the comment using a prepared statement
- Show a success message when the insert works
Keep learning
Related questions
Converting HTML and CSS to PDF in PHP: Core Concepts, Limits, and Practical Approaches
Learn how HTML-to-PDF conversion works in PHP, why CSS support varies, and how to choose practical approaches for reliable PDF output.
How PHP foreach Actually Works with Arrays
Learn how PHP foreach works internally, including array copies, internal pointers, by-value vs by-reference behavior, and common pitfalls.
How to Check String Prefixes and Suffixes in PHP
Learn how to check whether a string starts or ends with specific text in PHP using simple functions and practical examples.