Подготовленные выражения в PDO: пишем SELECT/INSERT безопасно и без инъекций (Mysql)

SQL‑инъекции — распространённая проблема: если вставлять пользовательские данные прямо в запрос, злоумышленник может исказить логику и получить доступ к чужим данным или испортить базу.

PDO (PHP Data Objects) позволяет защититься с помощью подготовленных выражений. Принцип простой: сначала задаёте шаблон запроса с параметрами (теми самыми плейсхолдерами), а потом отдельно передаёте туда значения. База данных воспринимает эти значения строго как данные, а не как часть SQL‑кода.


Как это выглядит на практике

SELECT с подготовленным выражением

Допустим, нужно получить пользователя по email. Небезопасный вариант выглядел бы так:

php
$email = $_GET['email'];
$sql = "SELECT * FROM users WHERE email = '$email'";

Если в email передать ' OR '1'='1, запрос вернёт всех пользователей.

С подготовленным выражением в PDO делают так:

php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
$email = $_GET['email'] ?? '';
 
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([':email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

Здесь :email — параметр. PDO сам корректно передаёт значение, исключая риск инъекции.


INSERT с подготовленным выражением

Для вставки данных логика та же: сначала шаблон, потом данные. Пример добавления пользователя:

php
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
 
$stmt = $pdo->prepare(
    'INSERT INTO users (name, email, created_at) VALUES (:name, :email, NOW())'
);
$stmt->execute([
    ':name' => $name,
    ':email' => $email,
]);

Такой INSERT не позволит внедрить вредоносный SQL через поля формы.


Какие параметры бывают

В PDO используют два основных стиля:

  • Именованные параметры: :name, :email. Удобно, когда параметров много — сразу понятно, что куда идёт.
  • Позиционные параметры: ?. Значения передаются в том же порядке, в каком стоят знаки вопроса.

Пример с позиционными параметрами:

php
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute([$name, $email]);

Именованные обычно читаются лучше и реже приводят к ошибкам при изменении запроса.


Частые ошибки, из‑за которых защита не работает

  • Склеивание запроса с пользовательскими данными. Даже если дальше используется prepare, риск остаётся, если часть запроса вы собираете вручную из ввода пользователя.
  • Попытка подставить имена таблиц или колонок через параметр. В PDO параметры работают только для значений. Имена таблиц и колонок нужно проверять по белому списку допустимых значений и вставлять явно:
    php
    $allowedTables = ['users', 'posts'];
    $table = in_array($_GET['table'], $allowedTables, true) ? $_GET['table'] : 'users';
    $stmt = $pdo->prepare("SELECT * FROM `$table` WHERE id = :id");
    $stmt->execute([':id' => $_GET['id']]);
  • Отключение обработки ошибок или игнорирование настроек безопасности. Не меняйте настройки PDO, отвечающие за обработку ошибок и экранирование, без чёткого понимания последствий.

Полезные приёмы

  • Получить сразу все строки можно через fetchAll:
    php
    $stmt = $pdo->prepare('SELECT id, name FROM users WHERE active = :active');
    $stmt->execute([':active' => 1]);
    $users = $stmt->fetchAll(PDO::FETCH_ASSOC);
  • Проверить, сколько строк затронуто, помогает rowCount() — это удобно после UPDATE, DELETE или INSERT:
    php
    $stmt = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
    $stmt->execute([':name' => $newName, ':id' => $id]);
    $affected = $stmt->rowCount();
  • Всегда указывайте кодировку соединения (charset=utf8mb4 в DSN) и включайте режим исключений (ERRMODE_EXCEPTION), чтобы ошибки не оставались незамеченными.
  • Подготовленные выражения защищают от SQL‑инъекций, но не закрывают другие риски: нужна ещё валидация данных, проверка прав доступа, ограничения на уровне базы и т. д.
  • Один подготовленный запрос можно выполнять многократно с разными значениями — это и удобно, и эффективнее по производительности.

Добавить комментарий