PHP – обнаружение ошибки / вставки mysql из-за нарушения уникального ограничения

Это похоже на этот вопрос:

PHP MySQL INSERT терпит неудачу из-за уникального ограничения

но у меня другой поворот. Допустим, у меня есть таблица с одним столбцом. Имя столбца – «title», и на нем есть уникальное ограничение.

Сначала я вставляю строку, где title = "something". В следующий раз, когда я попытаюсь вставить «что-то», он будет терпеть неудачу из-за уникального ограничения ключа (что хорошо). То, что я хотел бы сделать, это позволить ему выйти из строя и проверить код ошибки, предоставленный mysql, чтобы убедиться, что он не удалось из-за уникального ограничения ключа. (т. е. пусть база данных обрабатывает уникальность, и я просто обрабатываю код ошибки и сообщаю пользователю, что заголовок уже существует, когда возвращается результат).

Есть ли способ сделать это?

http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html

http://php.net/manual/en/function.mysql-errno.php

Я должен был сделать это в прошлом, и это не весело:

if( mysql_errno() == 1062) { // Duplicate key } else { // ZOMGFAILURE } 

Замечание о стиле программирования (Credits to jensgram из этого ответа)
Вы всегда должны стараться избегать использования магических чисел . Вместо этого вы можете присвоить известному коду ошибки ( 1062 ) константе (например, MYSQL_CODE_DUPLICATE_KEY ). Это упростит работу вашего кода, так как условие в выражении if еще можно прочитать за несколько месяцев, когда значение 1062 исчезло из памяти 🙂

Теперь, когда это 2015 год, есть очень мало причин не использовать PHP-реализацию PDO:

http://php.net/manual/en/book.pdo.php

Правильный, современный метод «OO» для обнаружения и обработки отказа вставки из-за нарушения ограничения ключа выглядит следующим образом:

 try { //PDO query execution goes here. } catch (\PDOException $e) { if ($e->errorInfo[1] == 1062) { //The INSERT query failed due to a key constraint violation. } } 

В объекте PDOException также есть много чего сказать о специфике ошибки (слишком подробно, чем можно было бы когда-либо хотеть или нужно, по-видимому).

http://php.net/PDOException

Я считаю, что код ошибки для дубликатов ключей – 1586. Если вы попытались выполнить запрос, а затем, при сбое, проверьте код ошибки с помощью mysql_errno () / mysqli :: errno () и сравните его с 1586, что должно сделать Это. Если это не 1586, проверьте, что это на самом деле, повторив код ошибки после вашего запроса.

Почему бы просто не выбрать сначала, чтобы увидеть, существует ли запись уже. Или полностью устраните ошибку, используя INSERT ON DUPLCATE KEY UPDATE, или даже используйте ключевое слово IGNORE mysql. Почему преднамеренно вызывают ошибку?

Тема представляет интерес для пользователей PHP / Mysql, поэтому позвольте мне изложить решение. пожалуйста, обратите внимание

    • Нет волшебного портативного способа сделать это
    • ситуация не уникальна для PHP, если вы хотите обнаружить нарушение уникального ключа DB2 с помощью openJPA – вам нужно восстановить аналогичную обработку

Предположим, у вас есть форма – где у вас есть поле «Имя»,

1) В таблице БД

Добавьте уникальное ограничение, например –

 alter table wb_org add constraint uniq_name unique(name); 

2) Скрипт обработчика формы

Сценарий обработчика формы должен передавать данные на уровень БД, и если есть какие-либо ошибки, уровень БД будет сигнализировать об этом как о DBException (Исключение определено нами). мы переносим код, отправляющий данные на уровень БД в блок try-catch (отображается только соответствующий код)

 try{ .... $organizationDao = new \com\indigloo\wb\dao\Organization(); $orgId = $organizationDao->create($loginId,$fvalues["name"]) ; .... } catch(UIException $ex) { .... // do UI exception handling } catch(DBException $ex) { $errors = array(); $code = $ex->getCode(); $message = $ex->getMessage(); // look for code 23000, our constraint name and keyword duplicate // in error message thrown by the DB layer // Util::icontains is just case-insensitive stripos wrapper if( ($code == 23000) && Util::icontains($message,"duplicate") && Util::icontains($message,"uniq_name")) { $errors = array("This name already exists!"); } else { // Not sure? show generic error $errors = array(" Error: doing database operation!") ; } // log errors Logger::getInstance()->error($ex->getMessage()); Logger::getInstance()->backtrace($ex->getTrace()); // store data in session to be shown on form page $gWeb->store(Constants::STICKY_MAP, $fvalues); $gWeb->store(Constants::FORM_ERRORS,$errors); // go back to form $fwd = base64_decode($fUrl); header("Location: " . $fwd); exit(1); }catch(\Exception $ex) { // do generic error handling } 

Обратите внимание, что вам нужно найти ex-> getCode () для вашей ситуации. Как и выше, уровень PDO фактически отбрасывает SQLSTATE 23000 как ex-> code (где фактический код ошибки mysql равен 1062). Код также может варьироваться от БД к БД. Такое же сообщение ex-> может также меняться. Было бы лучше обернуть эту проверку в одном месте и скрипку с помощью файла конфигурации.

3) внутри уровня БД (с использованием PDO)

  static function create($loginId, $name) { $dbh = NULL ; try { $dbh = PDOWrapper::getHandle(); //Tx start $dbh->beginTransaction(); ... // do DB operations //Tx end $dbh->commit(); $dbh = null; } catch(\Exception $ex) { $dbh->rollBack(); $dbh = null; throw new DBException($ex->getMessage(),$ex->getCode()); } 

4) Назад в форму (после удара формы Handler => DB Layer => Обработчик ошибок обработчика формы => Форма)

Извлечь сообщения об ошибках, установленные в сеансе, и отобразить их в форме.

5) Класс DBException

 <?php namespace com\indigloo\exception { class DBException extends \Exception { public function __construct($message,$code=0, \Exception $previous = null) { // PDO exception etc. can return strange string codes // Exception expects an integer error code. settype($code,"integer"); parent::__construct($message,$code,$previous); } } } ?> 

6) утилита icontains

  static function icontains($haystack, $needle) { return stripos($haystack, $needle) !== false; } 

Можем ли мы сделать это без исключений и PDO?

7) без PDO и использование только mysqli

Получить код ошибки и сообщение об ошибке из mysqli и выбросить DBException из уровня DB Обработчик DBException так же.

8) Можем ли мы это делать без использования исключений?

Я пишу это без какого-либо опыта работы с ним в реальном коде. Поэтому, пожалуйста, дайте мне знать, если вы не согласитесь. Также, пожалуйста, поделитесь, если у вас есть лучшая схема. если вы просто хотите, чтобы у вас был общий тип обработчика, то да.

  • внутри уровня БД: вызывать ошибки с помощью trigger_error вместо исключения исключений. внутри метода trigger_error – используйте некоторые MAGIC_STRING + DB_CODE
  • определить собственный обработчик ошибок для страницы обработчика форм
  • внутри вашего пользовательского error_handler для обработчика формы: найдите MAGIC_STRING + code
  • если вы получите код MAGIC_STRING +, тогда
    • установить соответствующее сообщение в сеансе
    • переслать страницу
    • отображение настраиваемого сообщения в сеансе

Проблема, с которой я сталкиваюсь с trigger_error и error_handlers, заключается в том, что

  • вы не можете ловить их в поток исполнения, как вы можете делать с исключениями. Однако это не проблема в нашем случае, потому что наш error_handler для страницы просто нужно перенаправить на страницу формы.
  • Я не знаю способ поднять конкретные коды ошибок (какой код я хочу) с помощью метода trigger_error. Если бы можно было сделать ошибку с кодом X и нашим сообщением Y. Насколько я знаю, вы не можете этого сделать. Вот почему мы восстанавливаем разбор каждого error_message, который получает наш error_handler.

У меня нет большого опыта работы с кодами ошибок (я был поднят на исключениях) – так может быть, кто-то еще может нас просветить.

Образцы кода из моего общедоступного github repo https://github.com/rjha/website – кода, который я пишу, создают создателя веб-сайта для запуска тысяч сайтов из того же БД. Приведенный выше код используется для проверки уникального имени для веб-сайта.

Из документации PHP по функции mysql_errno :

 Returns the error number from the last MySQL function, or 0 (zero) if no error occurred. 

Кроме того, из документации MySQL по ошибкам нарушения ограничений код ошибки 893 соответствует:

 Constraint violation eg duplicate value in unique index 

Итак, мы можем написать что-то подобное, чтобы сделать работу:

 if (!$result) { $error_code = mysql_errno(); if ($error_code == 893) { // Duplicate Key } else { // Some other error. } } 

Если вы знаете какой-то SQL, попробуйте это решение (протестировано)

 $username = "John"; $stmt = $pdo->prepare(" INSERT INTO users ( username ) SELECT * FROM ( SELECT :username ) AS compare WHERE NOT EXISTS ( SELECT username FROM users WHERE username = :username ) LIMIT 1; "); $stmt->bindParam(":username", $username); if ($stmt->execute()) { if ($stmt->rowCount() == 0) { echo "Dublicate Username, ".$username." already exists."; } else { echo $username." not in use yet."; } }