Introduction: The Question That Haunts Every PHP Developer
\n\nIf you've ever written SQL queries in PHP, you've probably heard the mantra: "Use PDO and prepared statements — and you'll be safe". But is that really the case? On Stack Overflow, the question "Are PDO prepared statements sufficient to prevent SQL injection?" has gathered thousands of views and dozens of heated discussions. And for good reason.
\n\nIn this article, we won't just give a short "yes" or "no" answer. We'll get under the hood of PDO, break down exactly how prepared queries work, and find out in which cases even PDO can let you down. Ready? Let's go.
\n\nWhat is SQL Injection and Why Is It Scary?
\n\nBefore talking about protection, let's recall the enemy. SQL injection is an attack where an attacker "injects" malicious SQL code through input data. The classic example:
\n\n// Bad code: never do this\n$username = $_POST['username'];\n$query = "SELECT * FROM users WHERE username = '$username'";\n$result = mysqli_query($conn, $query);\n\n\nIf an attacker passes admin' OR '1'='1, the query becomes:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'\n\n\nVoilà — access to data without a password. And if they pass '; DROP TABLE users; --? Goodbye, users table.
PDO and Prepared Queries: How Does It Work?
\n\nPDO (PHP Data Objects) offers a prepared statements mechanism. The idea is simple: you separate the query structure from the data. Instead of inserting variables directly into the string, you use placeholders:
\n\n// Safe code with PDO\n$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");\n$stmt->execute([':username' => $_POST['username']]);\n\n\nBut what happens under the hood? This is the key point that many people miss.
\n\nEmulation Mode vs. Native Prepared Statements
\n\nPDO can work in two modes:
\n\n- \n
- Emulation mode (default in older PHP versions) — PDO escapes the data itself and substitutes it into the query on the PHP side. This is still safe, but theoretically vulnerable if the escaping breaks (for example, when working with non-standard encodings). \n
- Native mode — PDO sends two messages to the database server: first the query template, then the data separately. The server itself processes the data as values, not as code. This is absolutely safe. \n
To enable native mode, disable emulation:
\n\n$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);\n\n\nNow the query is sent to the server in two stages. Even if an attacker passes ' OR '1'='1, the MySQL or PostgreSQL server simply treats it as a string value, not as part of the SQL code. Magic!
But Is Everything So Perfect? PDO Pitfalls
\n\nLet's return to the Stack Overflow question. The answer is "yes, PDO prepared statements are sufficient" — but with caveats. Here are situations where protection can fail.
\n\n1. Dynamic Identifiers (Table Names, Column Names)
\n\nPrepared statements only work for values (data). If you substitute a table or column name, placeholders won't help:
\n\n// Dangerous! Table name cannot be passed through a placeholder\n$stmt = $pdo->prepare("SELECT * FROM :table WHERE id = :id");\n$stmt->execute([':table' => $_GET['table'], ':id' => 1]); // Error!\n\n\nPDO will simply insert the table name as a string, and the query will break. If you use emulation, an attacker could pass users; DROP TABLE users; --. Solution: a whitelist of allowed values.
2. LIKE Queries and Wildcard Characters
\n\nIf you use