PHP PDO: подключение к MySQL и подготовленные запросы (понятное руководство)

Если вы начинаете работать с базами данных в PHP или хотите перейти на современный и безопасный способ общения с MySQL, используйте PDO. Он поддерживает подготовленные запросы (prepared statements), транзакции и единый интерфейс для разных СУБД. В статье — практический минимум, чтобы быстро и правильно стартовать с PHP PDO и подготовленными запросами.
Зачем использовать PDO
- Безопасность: подготовленные выражения защищают от SQL-инъекций по умолчанию.
- Универсальность: переключение между MySQL, PostgreSQL и др. почти без переписывания кода.
- Удобство: исключения, настройки ошибок, гибкие режимы выборки.
Подключение к MySQL через PDO
Настройте кодировку, ошибки и режим выборки сразу при подключении. Так вы избежите 80% проблем в будущем.
<?php
$dsn = 'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4';
$user = 'app_user';
$pass = 'secret';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // ошибки как исключения
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // массивы без дублей индексов
PDO::ATTR_EMULATE_PREPARES => false, // нативные prepared statements
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
// В проде логируйте, а не показывайте пользователю детали
die('DB error: ' . $e->getMessage());
}
Подготовленные запросы: основы
Prepared statements позволяют отделить SQL от данных. Это устраняет инъекции и упрощает повторные вызовы одного и того же запроса.
SELECT с именованными параметрами
<?php
$status = 'active';
$limit = 10;
$sql = 'SELECT id, email FROM users WHERE status = :status LIMIT :limit';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':status', $status, PDO::PARAM_STR);
$stmt->bindValue(':limit', (int)$limit, PDO::PARAM_INT); // для LIMIT/OFFSET используйте INT
$stmt->execute();
$users = $stmt->fetchAll();
Если ваш драйвер не поддерживает биндинг для LIMIT/OFFSET с отключённой эмуляцией, безопасная альтернатива — жёсткое приведение к int и интерполяция:
<?php
$limit = (int)$limit;
$sql = "SELECT id, email FROM users WHERE status = :status LIMIT $limit"; // безопасно после (int)
$stmt = $pdo->prepare($sql);
$stmt->execute([':status' => $status]);
INSERT с позиционными параметрами
<?php
$email = 'new@example.com';
$name = 'New User';
$sql = 'INSERT INTO users (email, name, created_at) VALUES (?, ?, NOW())';
$stmt = $pdo->prepare($sql);
$stmt->execute([$email, $name]);
$newId = (int)$pdo->lastInsertId();
UPDATE и счётчик затронутых строк
<?php
$sql = 'UPDATE users SET name = :name WHERE id = :id';
$stmt = $pdo->prepare($sql);
$stmt->execute([':name' => 'Alice', ':id' => 5]);
$updated = $stmt->rowCount(); // сколько строк обновлено
Как PDO защищает от SQL-инъекций
Ошибка новичка — склеивать SQL и данные.
<?php
// ПЛОХО: уязвимо к инъекциям
$email = $_GET['email'];
$q = $pdo->query("SELECT * FROM users WHERE email = '$email'");
// ПРАВИЛЬНО: используем prepare + параметры
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([':email' => $email]);
$user = $stmt->fetch();
Транзакции: надёжные изменения данных
Транзакции гарантируют целостность: либо все операции проходят, либо ни одна.
<?php
$from = 1; $to = 2; $sum = 100;
$pdo->beginTransaction();
try {
$dec = $pdo->prepare('UPDATE accounts SET balance = balance - :s WHERE id = :id');
$dec->execute([':s' => $sum, ':id' => $from]);
if ($dec->rowCount() !== 1) {
throw new RuntimeException('Не удалось списать средства');
}
$inc = $pdo->prepare('UPDATE accounts SET balance = balance + :s WHERE id = :id');
$inc->execute([':s' => $sum, ':id' => $to]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
// логируем и пробрасываем дальше
throw $e;
}
Динамический IN с массивом значений
Частая задача — выбрать записи по списку id. Решение — сгенерировать плейсхолдеры под каждое значение.
<?php
$ids = [3, 5, 9];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql = "SELECT id, name FROM products WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($sql);
$stmt->execute($ids);
$products = $stmt->fetchAll();
Эффективность: переиспользование подготовленного выражения
<?php
$userIds = [1,2,3,4,5];
$stmt = $pdo->prepare('UPDATE users SET last_login = NOW() WHERE id = ?');
foreach ($userIds as $id) {
$stmt->execute([$id]);
}
Отладка запросов
<?php
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->bindValue(':id', 10, PDO::PARAM_INT);
$stmt->execute();
// В DEV-среде можно посмотреть, что и как было подставлено
$stmt->debugDumpParams();
Важно: debugDumpParams предназначен для отладки и не должен светиться в продакшене.
Типичные ошибки и как их избежать
- Забыли установить ERRMODE_EXCEPTION — исключения не летят, ошибки тихо игнорируются.
- Склеивание SQL-строк с пользовательскими данными — используйте параметры везде, где можно.
- Неверный тип для LIMIT/OFFSET — применяйте (int) и PDO::PARAM_INT.
- Отключили транзакции при нескольких связанных изменениях — включайте их для целостности.
- Неправильная кодировка — всегда charset=utf8mb4 в DSN для работы с emoji и всеми символами.
Мини-чеклист перед продакшеном
- PDO::ATTR_ERRMODE = EXCEPTION
- PDO::ATTR_EMULATE_PREPARES = false (по возможности)
- charset=utf8mb4 в DSN
- Все внешние данные — через подготовленные параметры
- Транзакции вокруг связанных изменений
- Логи ошибок — пишем в файлы/мониторинг, не показываем пользователю
Куда двигаться дальше
Освоив подключение и подготовленные запросы, изучите пулы соединений, репозитории, миграции и профилирование. Если хотите системно прокачать PHP и базу данных, рекомендую практический путь с обратной связью: Пройти полный курс «PHP и MySQL с Нуля до Гуру 3.0» и закрыть пробелы быстрым темпом.
Теперь у вас есть база: безопасное подключение, подготовленные запросы, транзакции и типичные приёмы для реальных задач. Применяйте эти практики с первого дня — и ваш PHP-код станет надёжнее и быстрее.
-
-
Михаил Русаков
Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.