Отладка ошибок MySQL в PHP классе Engine: практические советы
Отладка ошибок MySQL в классе Engine
Как надёжно обрабатывать ошибки MySQL в классе Engine с использованием PDO?
Наиболее эффективным решением является перевод PDO в режим исключений и перехват их в блоках try / catch. Это позволяет унифицировать обработку всех ошибок базы данных, включая ошибки соединения, синтаксиса запросов и нарушения ограничений.
class Engine {
private $pdo;
public function __construct($dsn, $user, $pass) {
try {
$this->pdo = new PDO($dsn, $user, $pass);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
// Логирование и корректный выход
error_log('Engine connection error: ' . $e->getMessage());
throw new RuntimeException('Database connection failed');
}
}
public function query($sql) {
try {
return $this->pdo->query($sql);
} catch (PDOException $e) {
error_log('Engine query error: ' . $e->getMessage());
throw new RuntimeException('Query execution error');
}
}
}Engine classes mysql php at line (ошибка в классе engine при работе с mysql в php)
При таком подходе все ошибки MySQL преобразуются в исключения, которые можно обрабатывать централизованно. Это упрощает отладку и предотвращает неожиданное завершение скрипта.
Возможные проблемы:
- Исключение может быть перехвачено слишком рано и скрыть реальную причину сбоя. Рекомендуется логировать полное сообщение, а пользователю показывать общую информацию.
- Если в коде используется @ для подавления ошибок, исключения PDO могут не сработать. Следует избегать оператора подавления в методах работы с БД.
- При использовании транзакций исключение может оставить транзакцию открытой. Необходимо в catch выполнять откат.
Как использовать mysqli с проверкой ошибок в классе Engine?
Альтернативой PDO является расширение mysqli, которое предоставляет методы error и errno для получения информации об ошибке. Пример реализации:
class Engine {
private $mysqli;
public function __construct($host, $user, $pass, $db) {
$this->mysqli = new mysqli($host, $user, $pass, $db);
if ($this->mysqli->connect_errno) {
throw new RuntimeException('Connect error: ' . $this->mysqli->connect_error);
}
}
public function query($sql) {
$result = $this->mysqli->query($sql);
if (!$result) {
error_log('MySQL error: ' . $this->mysqli->error);
throw new RuntimeException('Query error [' . $this->mysqli->errno . ']');
}
return $result;
}
}Php mysql error (ошибка php mysql)
Этот вариант подходит, когда проект уже использует mysqli и нет необходимости переходить на PDO. Однако он менее гибкий в плане подготовки запросов и работы с разными СУБД.
Типичные ошибки:
- Забывают проверять connect_errno после создания соединения. Это приводит к неопределённому поведению.
- Использование error без проверки наличия ошибки – при успешном запросе свойство может содержать пустую строку.
Как получить полный контекст ошибки с помощью debug_backtrace?
Иногда требуется узнать не только текст ошибки, но и стек вызовов, приведший к ней. Это полезно при сложной архитектуре класса Engine с наследованием или цепочками вызовов.
class Engine {
public function query($sql) {
try {
// ... выполнение запроса
} catch (PDOException $e) {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$context = [
'message' => $e->getMessage(),
'sql' => $sql,
'trace' => $trace
];
error_log(json_encode($context));
throw $e; // или собственное исключение
}
}
}Такой подход позволяет сохранить в логе полную картину сбоя. Однако debug_backtrace потребляет ресурсы, поэтому его стоит применять только в отладочном режиме или при логировании критических ошибок.
Проблемы:
- Большой объём данных может забить лог. Следует ограничивать глубину или использовать флаг DEBUG_BACKTRACE_IGNORE_ARGS.
- В production-среде вывод полного стека может раскрыть внутреннюю структуру приложения. Не показывайте его пользователю.
Расширенные примеры отладки ошибок MySQL в классе Engine
Ниже приведены подробные примеры, демонстрирующие различные сценарии возникновения ошибок и способы их обработки.
Пример 1: Обработка ошибки дублирования ключа в PDO
class Engine {
private $pdo;
public function __construct() {
$this->pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function insertUser($email) {
try {
$stmt = $this->pdo->prepare('INSERT INTO users (email) VALUES (:email)');
$stmt->execute([':email' => $email]);
} catch (PDOException $e) {
if ($e->getCode() == 23000) { // Нарушение уникальности
// Специфичная обработка
error_log('Duplicate email: ' . $email);
throw new InvalidArgumentException('Email already exists');
}
throw $e; // Прочие ошибки пробрасываем дальше
}
}
}
$engine = new Engine();
try {
$engine->insertUser('user@example.com');
} catch (InvalidArgumentException $e) {
echo 'Пользователь с таким email уже существует.';
}Результат: при повторной вставке того же email будет выведено сообщение 'Пользователь с таким email уже существует.', а в лог попадёт запись о дубликате.
Пример 2: Логирование всех SQL запросов с временными метками
class Engine {
private $pdo;
private $logFile;
public function __construct($logFile = '/tmp/sql.log') {
$this->pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$this->logFile = $logFile;
}
public function query($sql) {
$start = microtime(true);
try {
$result = $this->pdo->query($sql);
} catch (PDOException $e) {
file_put_contents($this->logFile, date('Y-m-d H:i:s') . " ERROR: " . $e->getMessage() . "\nSQL: $sql\n", FILE_APPEND);
throw $e;
}
$elapsed = microtime(true) - $start;
file_put_contents($this->logFile, date('Y-m-d H:i:s') . " OK: время $elapsed сек.\nSQL: $sql\n", FILE_APPEND);
return $result;
}
}
$engine = new Engine();
try {
$engine->query('SELECT 1');
} catch (Exception $e) {
// Обработка
}Результат: в файл /tmp/sql.log будут записываться все выполненные запросы с отметкой успеха или ошибки.
Пример 3: Использование кодов ошибок MySQL для дифференцированной реакции
class Engine {
private $mysqli;
public function __construct() {
$this->mysqli = new mysqli('localhost', 'user', 'pass', 'test');
if ($this->mysqli->connect_errno) {
throw new RuntimeException('Connect failed: ' . $this->mysqli->connect_error);
}
}
public function execute($sql) {
if (!$this->mysqli->query($sql)) {
$errno = $this->mysqli->errno;
$error = $this->mysqli->error;
switch ($errno) {
case 1062: // Duplicate entry
error_log("Duplicate entry: $error");
return ['error' => 'duplicate', 'message' => 'Запись уже существует'];
case 1146: // Table not found
error_log("Table missing: $error");
return ['error' => 'missing_table', 'message' => 'Таблица не найдена'];
default:
error_log("MySQL error ($errno): $error");
throw new RuntimeException('Database error');
}
}
return true;
}
}
$engine = new Engine();
$result = $engine->execute('INSERT INTO users (id) VALUES (1)');
if (is_array($result)) {
echo $result['message']; // например, 'Запись уже существует'
}Результат: при ошибке дублирования возвращается массив с кодом 'duplicate', при отсутствии таблицы – 'missing_table'. Исключение выбрасывается только для нераспознанных ошибок.