PDO — это инструмент в PHP, чтобы общаться с базой данных. Он умеет работать с разными базами (MySQL, PostgreSQL и другими) и помогает делать это безопасно — чтобы злоумышленники не могли испортить данные через запросы.
Как подключиться к базе
Создаём объект PDO и передаём туда адрес базы, имя, пароль и настройки.
$dsn = 'mysql:host=localhost;dbname=test_db;charset=utf8mb4'; $user = 'db_user'; $pass = 'db_password'; try { $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); } catch (PDOException $e) { error_log('Ошибка подключения: ' . $e->getMessage()); http_response_code(500); echo json_encode(['error' => 'Ошибка сервера']); exit; }
Что значат настройки — простыми словами:
ERRMODE_EXCEPTION: если случится ошибка, PHP сразу скажет об этом через исключение. Так проще ловить ошибки в коде.FETCH_ASSOC: когда получаем данные из базы, они приходят как обычный массив с названиями полей:['id' => 1, 'name' => 'Ivan'].EMULATE_PREPARES => false: база сама будет правильно обрабатывать запросы. Это надёжнее.
Зачем нужны подготовленные выражения
Это способ делать запросы так, чтобы нельзя было «вставить» вредоносный код. Работает так: сначала готовим шаблон запроса, потом отдельно передаём данные. База сама всё правильно подставит.
Вариант с ? (по порядку)
$sql = 'SELECT * FROM users WHERE email = ? AND active = ?'; $stmt = $pdo->prepare($sql); $stmt->execute([$_POST['email'], 1]); $user = $stmt->fetch();
Здесь ? — это пустые места. Данные подставляются по порядку.
Вариант с :имя (по названиям)
Удобнее, если параметров много:
$sql = 'SELECT * FROM users WHERE email = :email AND active = :active'; $stmt = $pdo->prepare($sql); $stmt->execute([ 'email' => $_POST['email'], 'active' => 1, ]); $user = $stmt->fetch();
Можно писать ключи без двоеточия — PDO сам поймёт.
Примеры простых запросов
Получить одну строку (SELECT)
$sql = 'SELECT id, name, email FROM users WHERE id = :id'; $stmt = $pdo->prepare($sql); $stmt->execute(['id' => $_GET['id']]); $row = $stmt->fetch(); if ($row === false) { http_response_code(404); echo json_encode(['error' => 'Пользователь не найден']); exit; }
Получить несколько строк
Важно: LIMIT нельзя передавать как параметр в таком режиме. Поэтому берём число, проверяем и вставляем прямо в запрос.
Вставить запись (INSERT)
$sql = 'INSERT INTO users (email, name, created_at) VALUES (:email, :name, :created_at)'; $stmt = $pdo->prepare($sql); $stmt->execute([ 'email' => $_POST['email'], 'name' => $_POST['name'], 'created_at' => date('Y-m-d H:i:s'), ]); $lastId = $pdo->lastInsertId(); // ID новой строки
Обновить запись (UPDATE)
$sql = 'UPDATE users SET name = :name, email = :email WHERE id = :id'; $stmt = $pdo->prepare($sql); $stmt->execute([ 'name' => $_POST['name'], 'email' => $_POST['email'], 'id' => $_POST['id'], ]); $affected = $stmt->rowCount(); // сколько строк обновилось if ($affected === 0) { // Либо ID не существует, либо данные не изменились }
Удалить запись (DELETE)
$sql = 'DELETE FROM users WHERE id = :id'; $stmt = $pdo->prepare($sql); $stmt->execute(['id' => $_POST['id']]); $deleted = $stmt->rowCount(); if ($deleted === 0) { // Ничего не удалилось: такого ID нет или уже удалён }
Самое главное правило безопасности
Никогда не собирайте запрос вручную из пользовательских данных:
// Так нельзя — это опасно! $sql = "SELECT * FROM users WHERE email = '{$_POST['email']}'";
Если пользователь введёт хитрую строчку, он сможет сломать базу. Всегда используйте prepare и execute.
Транзакции: когда нужно сделать сразу несколько действий
Бывает, что надо выполнить два действия вместе: если одно не получилось — отменить и второе. Например, списать деньги и записать историю.
// Так нельзя — это опасно! $sql = "SELECT * FROM users WHERE email = '{$_POST['email']}'"; try { $pdo->beginTransaction(); $stmt = $pdo->prepare('UPDATE accounts SET balance = balance - :amount WHERE id = :id'); $stmt->execute(['amount' => $amount, 'id' => $userId]); $stmt = $pdo->prepare('INSERT INTO history (user_id, amount, type) VALUES (:id, :amount, :type)'); $stmt->execute(['id' => $userId, 'amount' => $amount, 'type' => 'withdraw']); $pdo->commit(); // подтверждаем оба действия } catch (PDOException $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); // отменяем всё, если что-то пошло не так } error_log('Ошибка транзакции: ' . $e->getMessage()); http_response_code(500); }
Как понимать, что пошло не так
С настройкой ERRMODE_EXCEPTION любая ошибка сразу превращается в исключение. Ловим его через try/catch:
try { $stmt = $pdo->prepare('SELECT * FROM non_existent_table'); $stmt->execute(); } catch (PDOException $e) { error_log('Ошибка БД: ' . $e->getMessage()); // пишем в лог http_response_code(500); echo json_encode(['error' => 'Произошла ошибка']); // пользователю — без деталей }
Пользователю не надо показывать текст ошибки — там может быть лишняя информация про базу.
Частые ошибки, которых лучше избегать
- Включать эмуляцию подготовленных выражений (
EMULATE_PREPARES = true). Лучше оставитьfalse. - Передавать через параметры имена таблиц, колонок,
LIMIT. Это не работает как надо. Проверяйте такие значения и вставляйте в запрос явно. - Не смотреть на
rowCount()вUPDATEиDELETE. Полезно знать, сколько строк реально затронуто. - Делать несколько связанных действий без транзакции. Если посередине случится ошибка, данные могут стать несогласованными.
Простой пример функций для пользователей
function getUserById(PDO $pdo, int $id): ?array { $sql = 'SELECT id, name, email, active FROM users WHERE id = :id'; $stmt = $pdo->prepare($sql); $stmt->execute(['id' => $id]); return $stmt->fetch() ?: null; } function createUser(PDO $pdo, string $name, string $email): int { $sql = 'INSERT INTO users (name, email, created_at) VALUES (:name, :email, :created_at)'; $stmt = $pdo->prepare($sql); $stmt->execute([ 'name' => $name, 'email' => $email, 'created_at' => date('Y-m-d H:i:s'), ]); return (int)$pdo->lastInsertId(); } function updateUser(PDO $pdo, int $id, string $name, string $email): bool { $sql = 'UPDATE users SET name = :name, email = :email WHERE id = :id'; $stmt = $pdo->prepare($sql); $stmt->execute([ 'name' => $name, 'email' => $email, 'id' => $id, ]); return $stmt->rowCount() > 0; } function deleteUser(PDO $pdo, int $id): bool { $sql = 'DELETE FROM users WHERE id = :id'; $stmt = $pdo->prepare($sql); $stmt->execute(['id' => $id]); return $stmt->rowCount() > 0; }
