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.