Reader Stacks

Connecting PHP to MySQL Without a Framework

A prepared statement isn't an optional refinement over string-concatenated SQL — it's the actual fix for SQL injection, which string escaping alone doesn't fully close off.

Connecting to MySQL directly in plain PHP, without a framework's ORM in the way, is worth understanding even in a Laravel-based career — PDO is the modern, recommended extension for this, and prepared statements are the actual fix for SQL injection, not an optional refinement.

Connecting with PDO

setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die('Connection failed: ' . $e->getMessage());
}

PDO::ATTR_ERRMODE set to ERRMODE_EXCEPTION is worth setting explicitly — without it, PDO silently returns false on a query failure by default rather than throwing, which makes errors far easier to miss during development.

Running a simple query

$stmt = $pdo->query('SELECT id, name FROM products');
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);

foreach ($products as $product) {
    echo $product['name'] . PHP_EOL;
}

The critical mistake: building queries with string concatenation

// NEVER DO THIS — vulnerable to SQL injection
$name = $_POST['name'];
$stmt = $pdo->query("SELECT * FROM products WHERE name = '{$name}'");

If $name contains something like ' OR '1'='1, this concatenated query's logic changes entirely — this is the exact mechanism behind a SQL injection attack, and it applies to any user-supplied value inserted directly into a query string, not just an obviously malicious-looking one.

The fix: prepared statements with bound parameters

$name = $_POST['name'];

$stmt = $pdo->prepare('SELECT * FROM products WHERE name = ?');
$stmt->execute([$name]);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);

A prepared statement sends the query structure and the actual data as two separate things to MySQL — the database engine itself then treats $name purely as a data value, never as part of the SQL syntax, which is what makes the injection technique above structurally impossible, not just harder to pull off.

Using named placeholders for readability

$stmt = $pdo->prepare('SELECT * FROM products WHERE category = :category AND price < :maxPrice');
$stmt->execute(['category' => 'electronics', 'maxPrice' => 100]);

Inserting a row

$stmt = $pdo->prepare('INSERT INTO products (name, price) VALUES (:name, :price)');
$stmt->execute(['name' => 'Wireless Mouse', 'price' => 29.99]);

$newId = $pdo->lastInsertId();

Why this matters even for a Laravel developer

Eloquent and the query builder handle parameter binding automatically behind the scenes — but understanding what's actually happening underneath (as shown here with raw PDO) is exactly why DB::raw() and manually concatenated raw SQL fragments inside an Eloquent query are just as vulnerable to injection as the plain PHP mistake above, despite being written inside a Laravel application.