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:
$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) и передаёшь данные отдельно:
Плохо (старый стиль, уязвимо):
// НЕ ДЕЛАЙ ТАК НИКОГДА $username = $_POST['username']; $sql = "SELECT * FROM users WHERE username = '$username'"; $result = mysql_query($sql); // уязвимо к SQL-инъекциям
Хорошо (PDO + подготовленный запрос):
$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 лучше задать пару настроек, чтобы ошибки не «проглатывались», а код был удобнее:
$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 и добавить запись в лог.
// 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, датами, флагами.
