Good Code
The good version prepares SQL once, binds the email as data, selects explicit columns, and returns null when no row matches.
Lesson 04
Use prepared statements with bound values instead of interpolating request data into SQL strings.
<?php declare(strict_types=1);
final class UserRepository
{
public function __construct(private PDO $pdo) {}
public function findActiveByEmail(string $email): ?array
{
// Placeholders keep SQL structure separate from user data.
$stmt = $this->pdo->prepare(
'SELECT id, email, name FROM users WHERE email = :email AND active = 1'
);
$stmt->execute(['email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
return $user === false ? null : $user;
}
}<?php
$email = $_GET['email'];
// Interpolating request data makes the query boundary unsafe.
$sql = "SELECT * FROM users WHERE email = '$email' AND active = 1";
$user = $pdo->query($sql)->fetch();The good version prepares SQL once, binds the email as data, selects explicit columns, and returns null when no row matches.
The bad version interpolates a request value into SQL and selects every column. A reviewer cannot trust the query boundary or the response shape.