← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

PDO

PHP PHP 5.1+ Intermediate
debt(d5/e3/b5/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and semgrep as the tools, and the code_pattern identifies three specific misconfiguration patterns (EMULATE_PREPARES not false, ERRMODE not EXCEPTION, no charset in DSN). These are specialist static analysis tools — not default linters, not compiler errors — so d5 is the right anchor.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix describes setting two constructor options (ERRMODE_EXCEPTION and EMULATE_PREPARES=false) at connection instantiation time. The common_mistakes are all correctable by adjusting PDO constructor arguments or connection setup in one place. This is a small, localised fix — slightly more than one line (multiple attributes), but confined to the connection setup code.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). PDO applies to web, cli, and queue-worker contexts — essentially all PHP database work. Misconfigurations (silent errors, emulated prepares) affect every query in the codebase and every developer who writes or debugs database code. However, fixing it is localised to the connection factory, so it does not reshape the entire architecture (not b7). The ongoing tax on debugging and security review across all DB-touching code justifies b5.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception is canonical: developers reasonably believe 'I am using PDO prepared statements, therefore I am protected from SQL injection,' but PDO::ATTR_EMULATE_PREPARES defaults to true (driver-dependent), meaning prepared statements may be silently emulated client-side — exactly the interpolation behaviour PDO is supposed to prevent. This directly contradicts the documented promise of the abstraction and is not an edge case but a default behaviour. Combined with ERRMODE_SILENT as the default (errors vanish), the 'obvious' setup is doubly wrong, pushing toward t7.

About DEBT scoring →

Also Known As

PHP PDO PHP Data Objects PDO extension

TL;DR

PHP Data Objects — a database abstraction layer supporting prepared statements across multiple database drivers.

Explanation

PDO provides a consistent interface to multiple database backends (MySQL, PostgreSQL, SQLite, etc.) through a unified API. It supports both named (:name) and positional (?) parameter placeholders in prepared statements. PDO::ATTR_EMULATE_PREPARES should be set to false to ensure the database — not PHP — handles the parameterisation. PDO throws PDOException on failure when PDO::ATTR_ERRMODE is set to PDO::ERRMODE_EXCEPTION.

Diagram

flowchart LR
    APP2[PHP App] --> PDO2[PDO layer<br/>database agnostic]
    PDO2 -->|DSN string| MYSQL2[(MySQL)]
    PDO2 -->|DSN string| PGSQL[(PostgreSQL)]
    PDO2 -->|DSN string| SQLITE[(SQLite)]
    subgraph Safe_Query_Flow
        PREP[prepare SQL with placeholders]
        BIND2[execute with values array]
        FETCH[fetch results as array object]
        PREP --> BIND2 --> FETCH
    end
    subgraph Prevents
        INJ[SQL injection<br/>params never interpreted as SQL]
    end
style PDO2 fill:#1f6feb,color:#fff
style PREP fill:#238636,color:#fff
style INJ fill:#238636,color:#fff

Common Misconception

Using PDO automatically prevents SQL injection. PDO with prepared statements prevents SQLi, but PDO also supports emulated prepared statements (PDO::ATTR_EMULATE_PREPARES) which do client-side interpolation — always disable emulation and use native prepared statements.

Why It Matters

PDO is the standard PHP database abstraction layer — it provides prepared statements, consistent error handling, and supports multiple databases with the same API. Using mysql_* functions or raw string concatenation instead is a security and maintainability failure.

Common Mistakes

  • Using PDO::ERRMODE_SILENT (the default) — errors fail silently and are nearly impossible to debug.
  • Leaving PDO::ATTR_EMULATE_PREPARES enabled — use native prepared statements for proper SQL injection protection.
  • Catching PDOException but swallowing it without logging — you lose all diagnostic information.
  • Creating a new PDO connection on every function call instead of sharing a single connection.

Code Examples

✗ Vulnerable
// Interpolated variables — SQL injection:
$id = $_GET['id'];
$stmt = $pdo->query("SELECT * FROM users WHERE id = $id");

// Prepared statement with PDO:
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute([':id' => (int)$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
✓ Fixed
$pdo = new PDO(
    'mysql:host=localhost;dbname=myapp;charset=utf8mb4',
    $_ENV['DB_USER'],
    $_ENV['DB_PASS'],
    [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false, // native prepared statements
    ]
);

// Named placeholders
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND active = :active');
$stmt->execute([':email' => $email, ':active' => 1]);
$user = $stmt->fetch();

// Insert + last insert ID
$stmt = $pdo->prepare('INSERT INTO orders (user_id, total) VALUES (?, ?)');
$stmt->execute([$userId, $total]);
$orderId = $pdo->lastInsertId();

// Transactions
$pdo->beginTransaction();
try { $pdo->commit(); } catch (\Throwable $e) { $pdo->rollBack(); throw $e; }

Added 15 Mar 2026
Edited 22 Mar 2026
Views 91
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 3 pings W 1 ping T 2 pings F 4 pings S 15 pings S 8 pings M 2 pings T 1 ping W 0 pings T 0 pings F 0 pings S 0 pings S 1 ping M 1 ping T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 0 pings M 2 pings T 0 pings W
No pings yet today
PetalBot 2
Scrapy 34 Perplexity 12 Amazonbot 8 Ahrefs 7 Google 4 SEMrush 4 Unknown AI 3 ChatGPT 2 Claude 2 PetalBot 2 Bing 1 Meta AI 1 Majestic 1
crawler 76 crawler_json 4 pre-tracking 1
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Low
⚡ Quick Fix
Use PDO with ERRMODE_EXCEPTION and EMULATE_PREPARES=false — real prepared statements, not emulated ones, provide true SQL injection protection
📦 Applies To
PHP 5.1+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
PDO::ATTR_EMULATE_PREPARES not set to false; ERRMODE not set to ERRMODE_EXCEPTION; no charset in DSN
Auto-detectable: ✓ Yes phpstan semgrep
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Low ✗ Manual fix Fix: Medium Context: File Tests: Update
CWE-89


✓ schema.org compliant