Что такое PDO и зачем его используют вместо старых функций (PHP)

PDO (PHP Data Objects) — это встроенное в PHP расширение для работы с базами данных. Оно даёт единый интерфейс, чтобы из PHP обращаться к разным СУБД: MySQL, PostgreSQL, SQLite, Oracle и другим.


В чём главная проблема старых функций (mysql_*)

Раньше часто писали через функции вида mysql_connect, mysql_query и т. п. Сейчас они полностью удалены из PHP (начиная с PHP 7.0), и на то есть серьёзные причины.

Главные минусы старого подхода:

  • Нет нормальной защиты от SQL‑инъекций. В старом коде данные просто вставляли в строку запроса. Если не экранировать всё вручную и идеально, злоумышленник может выполнить свой SQL.
  • Разные функции под разные базы. Для MySQL — один набор функций, для PostgreSQL — другой. Переехать на другую СУБД означало переписать почти весь код работы с БД.
  • Неудобная обработка ошибок. Ошибки возвращались как false или через отдельные функции, из‑за чего код получался громоздким и легко пропускал проблемы.
  • Процедурный стиль. Много функций, много аргументов, сложно поддерживать и тестировать.

Для PunBB и любых плагинов/интеграций это критично: там часто есть формы, поиск, вывод тем и пользователей — то есть много мест, где в запрос попадают данные от пользователя.


Что даёт PDO

Единый API под разные базы

Ты пишешь один и тот же код для разных СУБД, меняется только строка подключения (DSN). Например, для MySQL:

php
$dsn = 'mysql:host=localhost;dbname=punbb;charset=utf8mb4';
$user = 'db_user';
$pass = 'db_pass';
 
$pdo = new PDO($dsn, $user, $pass);

А для PostgreSQL будет почти то же самое, только mysql: заменится на pgsql:. Логика работы с запросами, выборками и ошибками остаётся одинаковой. Это сильно упрощает миграции и поддержку.

Защита от SQL‑инъекций через подготовленные выражения

Это самое важное для безопасности PunBB‑плагинов. Вместо того чтобы собирать запрос строками, ты используешь плейсхолдеры (? или :name) и передаёшь данные отдельно:

Плохо (старый стиль, уязвимо):

php
// НЕ ДЕЛАЙ ТАК НИКОГДА
$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";
$result = mysql_query($sql); // уязвимо к SQL-инъекциям

Хорошо (PDO + подготовленный запрос):

php
$username = $_POST['username'];
 
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username');
$stmt->execute([':username' => $username]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Даже если в $username будет вредоносный текст, он уйдёт в базу как данные, а не как часть SQL. Это работает на уровне драйвера и надёжно закрывает основной вектор атак.


Как правильно настроить PDO (минимум, который нужен в реальных задачах)

Сразу после создания объекта PDO лучше задать пару настроек, чтобы ошибки не «проглатывались», а код был удобнее:

php
$dsn = 'mysql:host=localhost;dbname=punbb;charset=utf8mb4';
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, // ошибки как исключения
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,       // сразу ассоциативные массивы
    PDO::ATTR_EMULATE_PREPARES   => false,                   // нативные подготовленные запросы
];
 
try {
    $pdo = new PDO($dsn, 'db_user', 'db_pass', $options);
} catch (PDOException $e) {
    // В реальном проекте не выводим $e->getMessage() пользователю!
    error_log($e->getMessage());
    die('Ошибка подключения к базе данных.');
}

Что эти настройки дают:

  • ERRMODE_EXCEPTION — вместо «тихого» false при ошибке ты получаешь исключение. Это позволяет централизованно обрабатывать ошибки и не пропускать их.
  • FETCH_ASSOC — fetch() сразу вернёт массив вида ['id' => 1, 'username' => 'test'], а не нумерованный.
  • EMULATE_PREPARES => false — используются нативные подготовленные выражения СУБД. Это безопаснее и ближе к тому, как база реально выполняет запросы.

Пример: простой SELECT и INSERT на PDO

Допустим, нужно получить пользователя по ID и добавить запись в лог.

php
// SELECT по ID
$userId = (int)$_GET['id'];
$stmt = $pdo->prepare('SELECT id, username, email FROM users WHERE id = ?');
$stmt->execute([$userId]);
$user = $stmt->fetch(); // вернёт одну строку или false
 
if ($user) {
    // работаем с $user['username'] и т. д.
}
 
// INSERT в лог
$ip = $_SERVER['REMOTE_ADDR'];
$action = 'login_attempt';
 
$stmt = $pdo->prepare(
    'INSERT INTO logs (ip, action, created_at) VALUES (?, ?, NOW())'
);
$stmt->execute([$ip, $action]);

Всё коротко, без лишних функций, и безопасно.


Частые ошибки при переходе на PDO

  • Пытаются использовать mysql_real_escape_string вместе с PDO. Не надо: подготовленные выражения сами экранируют данные. Дублирование защиты только путает и может сломать логику.
  • Игнорируют обработку исключений. Если ERRMODE стоит в EXCEPTION, любая ошибка БД вызовет исключение. Без try/catch страница упадёт с ошибкой PHP.
  • Смешивают стили. Не стоит писать часть кода через query(), часть через prepare(). Для любого запроса с данными — только prepare()/execute().
  • Забывают про типы данных. Привязка параметров не меняет типы автоматически: число может уйти как строка, если не привести явно. Для PunBB это важно при работе с ID, датами, флагами.

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