Практика использования PDO для работы с базами данных в PHP

Раздел: Работа с базами данных в PHP -> PDO

Основы 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

PDO запросы в PHP - comments

En
Php pdo запрос (php)