Типичные ошибки PDO и методы их предотвращения
Ошибки PDO в PHP: методы обработки
PDO (PHP Data Objects) предоставляет единый интерфейс для работы с базами данных. Обработка ошибок имеет решающее значение для стабильности приложения. Рассмотрим основные подходы, начиная с рекомендуемого.
Как обеспечить надёжную обработку ошибок PDO?
Самый эффективный способ - установить режим исключений (ERRMODE_EXCEPTION) и обрабатывать исключения в блоке try/catch. Это позволяет централизованно управлять ошибками и избегать неожиданного завершения скрипта.
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query('SELECT * FROM non_existent_table');
} catch (PDOException $e) {
echo 'Ошибка: ' . $e->getMessage();
}Php pdo sqlsrv (pdo для sql server)
Пояснение: При возникновении ошибки (например, таблица не существует) PDO выбрасывает исключение PDOException. Блок catch перехватывает его, и можно вывести сообщение или записать в лог.
Типичные проблемы: Необработанное исключение (если не используется try/catch) приводит к фатальной ошибке. Также важно правильно указывать DSN и проверять наличие драйвера.
Как подавить вывод ошибок PDO и проверять их вручную?
Режим ERRMODE_SILENT не выводит и не выбрасывает исключения. Ошибки можно проверить методами errorCode() и errorInfo().
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$stmt = $pdo->query('SELECT * FROM missing_table');
if ($stmt === false) {
$error = $pdo->errorInfo();
echo 'Код: ' . $error[0] . ', сообщение: ' . $error[2];
}Php artisan db (команда artisan db в laravel)
Проблемы: Легко забыть проверить результат запроса, и ошибка останется незамеченной. Ручная проверка каждого запроса увеличивает код.
Как получать предупреждения PDO без остановки скрипта?
Режим ERRMODE_WARNING выводит предупреждения (warning), но выполнение продолжается. Полезно для отладки.
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$stmt = $pdo->query('SELECT * FROM fake_table');
echo 'Скрипт продолжает работу';Php pdo pgsql (pdo для postgresql)
Проблемы: Предупреждения могут быть скрыты настройками error_reporting, и разработчик их не увидит. Не подходит для продакшена.
Как получить детальную информацию об ошибке PDO после выполнения запроса?
Метод errorInfo() возвращает массив из трёх элементов: SQLSTATE код, код драйвера и сообщение. Можно использовать с любым режимом ошибок.
$stmt = $pdo->prepare('INSERT INTO users (name) VALUES (:name)');
$stmt->execute([':name' => 'test']);
if ($stmt->errorCode() !== '00000') {
$info = $stmt->errorInfo();
echo 'SQLSTATE: ' . $info[0] . ', Driver code: ' . $info[1] . ', Message: ' . $info[2];
}Create table php (создание таблицы в php)
Проблемы: Необходимо проверять errorCode() после каждого execute. Можно пропустить ошибку, если не вызывать errorInfo().
Как обработать неудачное соединение с БД через PDO?
Ошибка подключения - это исключение PDOException, даже если режим ошибок установлен в silent. Рекомендуется оборачивать создание объекта PDO в try/catch.
try {
$pdo = new PDO('mysql:host=invalid_host;dbname=test', 'user', 'pass');
} catch (PDOException $e) {
echo 'Не удалось подключиться: ' . $e->getMessage();
}Select from users php (sql запрос select from users в php)
Проблемы: Неверные учётные данные, недоступный хост, не установлен драйвер PDO. Важно проверять сообщение исключения и не выводить его в продакшене без фильтрации.
Как обрабатывать ошибки PDO внутри транзакций?
При работе с транзакциями ошибки могут нарушить целостность данных. Лучший подход - использовать исключения и откатывать транзакцию в блоке catch.
$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 (PDOException $e) {
$pdo->rollBack();
echo 'Транзакция откачена: ' . $e->getMessage();
}
Проблемы: Если забыть rollBack, база может остаться в заблокированном состоянии. Также нужно учитывать, что некоторые ошибки (например, deadlock) требуют повторной попытки.
Общие типичные ошибки PDO:
- could not find driver - отсутствует расширение PDO для нужной БД (php_pdo_mysql и т.д.).
- invalid data source name - неправильный формат DSN (например, пропущен двоеточие).
- SQLSTATE[23000] - нарушение уникальности (дубликат ключа).
- SQLSTATE[HY000] - общая ошибка драйвера (например, превышение соединений).
Решение: проверять настройки php.ini, правильность DSN, использовать подготовленные запросы для избегания ошибок синтаксиса.
Расширенные примеры обработки ошибок PDO
Логирование ошибок PDO в файл
В продакшен-среде ошибки не должны показываться пользователю. Удобно записывать их в лог с деталями.
function handlePdoError(PDOException $e, PDO $pdo = null) {
$log = date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL;
if ($pdo) {
$log .= 'Last query: ' . ($pdo->lastInsertId() ?? 'N/A') . PHP_EOL;
}
file_put_contents('/var/log/pdo_errors.log', $log, FILE_APPEND);
}
try {
$pdo = new PDO('...');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('INSERT INTO log (message) VALUES ("test")');
} catch (PDOException $e) {
handlePdoError($e, $pdo ?? null);
echo 'Произошла внутренняя ошибка.';
}
(Лог-файл будет содержать запись с временем и сообщением об ошибке)
Обработка ошибок при массовой вставке с транзакцией
При вставке нескольких записей в транзакции можно перехватить конкретный SQLSTATE и продолжить.
$pdo->beginTransaction();
try {
foreach ($users as $user) {
$stmt = $pdo->prepare('INSERT INTO users (email) VALUES (:email)');
$stmt->execute([':email' => $user['email']]);
}
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
if ($e->getCode() == 23000) {
echo 'Обнаружен дубликат email. Проверьте данные.';
} else {
echo 'Ошибка базы данных: ' . $e->getMessage();
}
}
При дубликате: "Обнаружен дубликат email. Проверьте данные."
Использование пользовательского класса исключений
Можно создать собственное исключение для более гибкой обработки.
class DatabaseException extends \Exception {}
try {
$pdo = new PDO('...');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->query('SELECT 1');
} catch (PDOException $e) {
throw new DatabaseException('Ошибка БД: ' . $e->getMessage(), (int)$e->getCode(), $e);
}
Выбрасывается исключение DatabaseException, которое можно перехватить выше.
Сравнение поведения разных режимов ошибок
Наглядный тест для понимания различий.
$modes = [
PDO::ERRMODE_SILENT,
PDO::ERRMODE_WARNING,
PDO::ERRMODE_EXCEPTION,
];
foreach ($modes as $mode) {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, $mode);
echo "Режим: $mode\n";
$stmt = $pdo->query('SELECT * FROM fake');
if ($stmt === false) echo 'Запрос вернул false' . PHP_EOL;
echo '---' . PHP_EOL;
}
Режим: 0 (silent) - вывод "Запрос вернул false" Режим: 1 (warning) - предупреждение и "Запрос вернул false" Режим: 2 (exception) - фатальная ошибка, скрипт прерывается
Обработка ошибок с учётом SQLSTATE кода
Разные ошибки требуют разной реакции.
try {
$stmt = $pdo->prepare('INSERT INTO users (login) VALUES (:login)');
$stmt->execute([':login' => $login]);
} catch (PDOException $e) {
switch ($e->getCode()) {
case '23000':
echo 'Логин уже занят.';
break;
case '22001':
echo 'Слишком длинное значение.';
break;
default:
echo 'Неизвестная ошибка: ' . $e->getMessage();
}
}
При дубликате: "Логин уже занят."