Практика использования PDO для работы с базами данных в PHP
Основы PDO запросов в PHP
PDO (PHP Data Objects) предоставляет универсальный интерфейс для работы с различными базами данных. Главное преимущество – использование подготовленных запросов, которые защищают от SQL-инъекций и повышают производительность при многократном выполнении.
Рекомендуемый подход: подготовленные запросы (prepared statements)
Подготовленные запросы отделяют структуру SQL от данных. Сначала подготавливается запрос с плейсхолдерами (?: или именованными :name), затем он выполняется с конкретными значениями. Это исключает возможность внедрения вредоносного кода.
<?php
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8';
$user = 'root';
$pass = '';
try {
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('SELECT id, name FROM users WHERE email = :email');
$stmt->execute(['email' => 'user@example.com']);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($user);
} catch (PDOException $e) {
echo 'Ошибка: ' . $e->getMessage();
}
?>
Php pdo запрос (pdo запросы в php)
В примере устанавливается соединение, включается режим выброса исключений, подготавливается SELECT, выполняется с массивом параметров и извлекается результат. При ошибке перехватывается исключение.
Типичные проблемы:
- Неправильные данные для подключения – проверяйте DSN, логин и пароль.
- Отсутствие драйвера PDO для используемой СУБД – установите расширение php_pdo_mysql (или другого).
- Забыли установить режим ошибок – по умолчанию PDO молча возвращает false.
Как выполнить простой запрос без внешних параметров с помощью PDO?
Цель: выполнить статический SQL запрос, который не содержит пользовательских данных. Случай: получение списка статей или конфигураций.
$stmt = $pdo->query('SELECT * FROM articles WHERE status = 1');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// обработка
}
Метод query() возвращает объект PDOStatement. Однако query не подходит для запросов с динамическими данными.
Ошибки:
- Если в запрос конкатенировать пользовательский ввод, возникает угроза SQL-инъекции.
- При ошибке query возвращает false, а не исключение (если не установлен режим исключений).
- Невозможно повторно использовать запрос с другими значениями.
Как вставить новую запись в таблицу через PDO?
Цель: безопасно добавить данные, переданные из формы. Случай: регистрация пользователей.
$stmt = $pdo->prepare('INSERT INTO users (name, email, password) VALUES (:name, :email, :password)');
$stmt->execute([
'name' => $_POST['name'],
'email' => $_POST['email'],
'password' => password_hash($_POST['password'], PASSWORD_DEFAULT)
]);
Пароль хешируется перед вставкой. Использование именованных плейсхолдеров улучшает читаемость.
Распространенные проблемы:
- Несоответствие типов: например, вставка строки в числовое поле.
- Попытка вставить дубликат уникального ключа – возникает исключение Integrity constraint violation.
- Отсутствие обязательных полей – нужно проверять входные данные.
Как обновить или удалить записи через PDO?
Цель: модификация существующих данных. Случай: изменение профиля пользователя, удаление устаревших записей.
// Обновление
$stmt = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
$stmt->execute(['name' => $newName, 'id' => $userId]);
// Удаление
$stmt = $pdo->prepare('DELETE FROM users WHERE id = :id');
$stmt->execute(['id' => $userId]);
После выполнения можно узнать количество затронутых строк через rowCount().
Типичные ошибки:
- Условие WHERE не ограничивает запрос – могут измениться или удалиться все строки. Нужно всегда проверять WHERE и использовать LIMIT при необходимости.
- После UPDATE rowCount может быть 0, если значения не изменились (не ошибка, но нужно учитывать).
Как получить данные из базы: одну строку или все строки?
Цель: извлечение результата запроса. Случай: отображение списка товаров или деталей одного товара.
// Одна строка
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$productId]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
// Все строки
$stmt = $pdo->query('SELECT * FROM products');
$products = $stmt->fetchAll(PDO::FETCH_OBJ);
Режимы FETCH_ASSOC возвращают ассоциативный массив, FETCH_OBJ – объект. Выбор зависит от стиля кода.
Возможные проблемы:
- fetch() возвращает false, если строк нет – нужно проверять.
- fetchAll() при большом количестве строк может потреблять много памяти.
Как обрабатывать ошибки PDO?
Цель: отлавливать и логировать ошибки базы данных. Случай: продакшн – важно видеть ошибки без раскрытия чувствительной информации.
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
// запросы
} catch (PDOException $e) {
// запись в лог, показ общего сообщения
error_log($e->getMessage());
echo 'Произошла ошибка базы данных.';
}
Также можно использовать ERRMODE_WARNING, но исключения удобнее для обработки.
Ошибки при настройке:
- Забыли установить режим ошибок – тогда PDO не сообщает о проблемах, возвращая false.
- В try-catch блоке не перехватывается PDOException – ошибка приведет к фатальной ошибке.
Как работать с транзакциями в PDO?
Цель: выполнить несколько запросов атомарно – либо все успешно, либо ни одного. Случай: перевод денег, добавление заказа с обновлением остатков.
$pdo->beginTransaction();
try {
$pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
$pdo->exec('UPDATE accounts SET balance = balance + 100 WHERE id = 2');
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
echo 'Транзакция отменена: ' . $e->getMessage();
}
После commit изменения становятся постоянными. rollBack отменяет все операции с момента beginTransaction.
Типичные ошибки:
- Забыли вызвать commit – изменения не сохранятся.
- Попытка начать транзакцию внутри другой транзакции (некоторые СУБД не поддерживают вложенные транзакции).
- Использование exec с некорректным SQL – нужно всегда проверять.
Расширенные примеры использования PDO
1. Различные режимы выборки fetch
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// FETCH_ASSOC
$stmt = $pdo->query('SELECT id, name FROM users');
$usersAssoc = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo 'FETCH_ASSOC: ';
print_r($usersAssoc);
// FETCH_OBJ
$stmt = $pdo->query('SELECT id, name FROM users');
$usersObj = $stmt->fetchAll(PDO::FETCH_OBJ);
echo 'FETCH_OBJ: ';
print_r($usersObj);
// FETCH_COLUMN – получить все имена
$stmt = $pdo->query('SELECT name FROM users');
$names = $stmt->fetchAll(PDO::FETCH_COLUMN);
echo 'Имена: ';
print_r($names);
FETCH_ASSOC: Array ( [0] => Array ( [id] => 1 [name] => Иван ) [1] => Array ( [id] => 2 [name] => Петр ) ) FETCH_OBJ: Array ( [0] => stdClass Object ( [id] => 1 [name] => Иван ) [1] => stdClass Object ( [id] => 2 [name] => Петр ) ) Имена: Array ( [0] => Иван [1] => Петр )
2. Транзакции с откатом при ошибке
$pdo->beginTransaction();
try {
$pdo->exec("UPDATE accounts SET balance = balance - 200 WHERE id = 1");
// Имитация ошибки: неверный запрос
$pdo->exec("UPDATE accounts SET balance = balance + 200 WHERE id = 'нечисло'");
$pdo->commit();
echo 'Транзакция выполнена';
} catch (PDOException $e) {
$pdo->rollBack();
echo 'Транзакция отменена: ' . $e->getMessage();
}
Транзакция отменена: SQLSTATE[22007]: Invalid datetime format: 1292 Truncated incorrect integer value: 'нечисло'
3. Массовая вставка с подготовленным запросом
$stmt = $pdo->prepare('INSERT INTO logs (message, date) VALUES (:msg, NOW())');
$messages = ['Событие 1', 'Событие 2', 'Событие 3'];
foreach ($messages as $msg) {
$stmt->execute(['msg' => $msg]);
}
echo 'Вставлено строк: ' . count($messages);
Вставлено строк: 3
4. Получение количества затронутых строк (rowCount)
$stmt = $pdo->prepare('UPDATE users SET last_login = NOW() WHERE id > :minId');
$stmt->execute(['minId' => 10]);
echo 'Обновлено строк: ' . $stmt->rowCount();
Обновлено строк: 5
5. Получение ID последней вставленной записи
$pdo->prepare('INSERT INTO users (name) VALUES (:name)')->execute(['name' => 'Новый']);
$newId = $pdo->lastInsertId();
echo 'ID новой записи: ' . $newId;
ID новой записи: 7
6. Именованные и позиционные плейсхолдеры
// Именованные
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = :id AND price < :price');
$stmt->execute(['id' => 3, 'price' => 1000]);
$product = $stmt->fetch();
// Позиционные
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ? AND price < ?');
$stmt->execute([3, 1000]);
$product = $stmt->fetch();
Оба варианта безопасны, именованные удобнее при большом количестве параметров.
7. Обработка ошибок с исключениями
try {
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('INVALID SQL');
} catch (PDOException $e) {
echo 'Код ошибки: ' . $e->getCode() . '<br>';
echo 'Сообщение: ' . $e->getMessage();
}
Код ошибки: 42000<br>Сообщение: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INVALID SQL' at line 1