Detect mysql update/insertion failure due to violated unique constraint - php

This is kind of similar to this question:
PHP MySQL INSERT fails due to unique constraint
but I have a different twist. Let's say I have a table with only one column. The column's name is "title" and it has a unique constraint placed on it.
First I insert a row where title = "something". The next time I try to insert "something" it will fail due to a unique key constraint (which is good). What I'd like to do is allow it to fail, and check the error code provided by mysql to ensure it failed due to a unique key constraint. (i.e. let the database handle the uniqueness, and I just handle the error code and tell the user that title already exists when the result comes back).
Is there a way to do this?

Now that it's the year 2015, there are very few reasons not to be using PHP's PDO implementation.
The proper, modern, "OO" method for detecting and handling an insertion failure due to a key constraint violation is as follows:
try {
//PDO query execution goes here.
}
catch (\PDOException $e) {
if ($e->errorInfo[1] == 1062) {
//The INSERT query failed due to a key constraint violation.
}
}
The PDOException object has a lot more to say about the specific nature of the error, too (more detail than one could possibly ever want or need, seemingly).

http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
http://php.net/manual/en/function.mysql-errno.php
I've had to do this in the past, and it's not fun:
if( mysql_errno() == 1062) {
// Duplicate key
} else {
// ZOMGFAILURE
}
A note on programming style (Credits to jensgram from this answer)
You should always seek to avoid the use of magic numbers. Instead, you could assign the known error code (1062) to a constant (e.g. MYSQL_CODE_DUPLICATE_KEY). This will make your code easier to maintain as the condition in the if statement is still readable in a few months when the meaning of 1062 has faded from memory :)

I believe the error code for duplicate keys is 1586. If you were to attempt to execute a query and then, on failure, check the error code using mysql_errno()/mysqli::errno() and compare it to 1586, that should do it. If it's not 1586, check what it actually is by echoing the error code after your query.

Why not just do a select first to see if the entry already exists. Or suppress an error altogether by using INSERT ON DUPLCATE KEY UPDATE, or even use the mysql IGNORE keyword. Why purposely cause an error?

Topic is of interest for fellow PHP/Mysql users so let me outline a solution.
Please note
There is no magical portable way to do it
situation is not unique to PHP, if you want to detect DB2 unique key constraint violation with openJPA - you have to restore to similar kind of handling
Suppose you have a form - where you have a field "Name"
1) In the DB table
Add a unique constraint like -
alter table wb_org add constraint uniq_name unique(name);
2 ) The form handler script
The form handler script should pass the data to DB layer and if there are any errors, the DB layer would signal it as an DBException (An exception defined by us). we wrap the code sending data to DB layer in a try-catch block (only relevant code is shown)
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
}
Please note that you have to find the ex->getCode() for your situation. Like in above, the PDO layer is actually throwing back the SQLSTATE 23000 as ex->code ( where the actual mysql error code is 1062). The code can vary from DB to DB also. Same way ex->message can also vary. It would be better to wrap this check in one place and fiddle using a configuration file.
3) inside DB layer (using 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) Back on the form (after hitting form Handler => DB Layer => Form Handler error handler => Form)
Extract error messages set in session and display them on the form.
5) DBException class
<?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 utility method
static function icontains($haystack, $needle) {
return stripos($haystack, $needle) !== false;
}
Can we do this without exceptions and PDO?
7) without PDO and using only mysqli
Get error code and error message from mysqli and throw DBException from DB layer
Handler the DBException same way.
8) Can we do this w/o using exceptions?
I am writing this without any experience of actually doing it in live code. So please let me know if you do not agree. Also, please share if you have a better scheme. if you just want a catch-it-all generic sort of handler then yes.
inside the DB layer: raise errors using trigger_error instead of throwing exceptions. inside trigger_error method - use some MAGIC_STRING + DB_CODE
define a custom error handler for form handler page
inside your custom error_handler for form handler : look for MAGIC_STRING + code
if you get MAGIC_STRING + code then
set appropriate message in session
forward to form page
display a custom message set in session
The problem I find with trigger_error and error_handlers is that
you cannot trap them in the flow of execution like you can do with exceptions. However this is not a problem in our case because our error_handler for page just needs to redirect to form page.
I do not know a way to raise specific error codes (what code I want) with trigger_error method. If only it were possible to raise an error with code X and our message Y. So far as I know you cannot do that. That is why we are restoring to parsing every error_message that our error_handler receives.
I do not have much experience working with error codes (I have been raised on exceptions) - so maybe someone else can enlighten us.
The code samples are from my public github repo https://github.com/rjha/website - code that I am writing for create a website builder to launch thousands of sites from same DB. The code above is used to check unique name for a website.

From PHP Documentation on the function mysql_errno:
Returns the error number from the last MySQL function,
or 0 (zero) if no error occurred.
Also, from MySQL Documentation on Constraint Violation Errors, error code 893 corresponds to:
Constraint violation e.g. duplicate value in unique index
So, we can then write something like this to do the work:
if (!$result) {
$error_code = mysql_errno();
if ($error_code == 893) {
// Duplicate Key
} else {
// Some other error.
}
}

If you know some SQL, try this solution (tested)
$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.";
}
}

Related

Calling SQL rollback on php exception

Consider the following scenario:
I open a new connection to MySQL, using mysqli extension
I start a transaction
I do some queries on the MySQL database and to some php computation
Either MySQL or php may throw some exceptions
I commit the transaction
(pseudo) code:
$sql = new mysqli();
...
{ //some scope
$sql->query("START TRANSACTION");
...
if (something)
throw Exception("Something went wrong");
...
$sql->query("COMMIT"); // or $sql->commit()
} //leave scope either normally or through exception
...
$sql->query( ... ); // other stuff not in transaction
If all the above is in some big try catch block (or perhaps even the exception is uncaught) the transaction will remain unfinished. If I then try to do some more work on the database, those actions will belong to the transaction.
My question is: is there some possible mechanism that would permit me to automatically send $sql->rollback() when I leave the transaction-creating scope in an abnormal way?
Some notes:
mysqli::autocommit is not the same as a transaction. It is just a setting for autocommit you turn on or off. Equivalent to MySQL SET autocommit.
mysqli::begin_transaction is not documented and I don't know what is its precise behavior. Will it call rollback when the mysqli object dies for example?
The Command mysqli::begin_transaction is the same (object oriented way) as your $sql->query("START TRANSACTION");;
There is no way, to auto rollback on exception.
You can only comit, if everything has success. Then it will be a "auto rollback" if not. But this way, you will have trouble very soon, if you have more then one commit.
So your current code is allready very good. I would do it the full OOP way:
$sql->begin_transaction();
try {
$sql->query('DO SOMETHING');
if(!true) {
throw new \Exception("Something went wrong");
}
$sql->commit();
}
catch (\Exception exception) {
$sql->rollback();
}
You also can write your own Exception:
class SqlAutoRollbackException extends \Exception {
function __construct($msg, Sql $sql) {
$sql->rollback();
parent::__construct($msg);
}
}
But you still need to catch it.
You cannot automatically rollback on exception, but with a little code you can do what you want. Even if you already are in a try-catch block you can nest them for your transaction section, such as this:
try {
$sql->query("START TRANSACTION");
...
if (something)
throw new PossiblyCustomException("Something went wrong");
...
$sql->query("COMMIT");
} catch (Exception $e) { //catch all exceptions
$sql->query("ROLLBACK");
throw $e; //rethrow the very same exception object
}
Even if you use the most generic catch Exception, the actual type of the exception is known. When you rethrow, it can still be caught by PossiblyCustomException later. Thus, all the handling you already have remains unaffected by this new try-catch block.
So you want to either commit the transaction or rollback the transaction if there was a problem, and there are many ways that a problem could occur - either through a mysql error or php throwing an exception. So why not set up a flag at the start of the transaction, set that flag when the transaction ready to be committed, and check the state of the flag when the process is done to see if a rollback is needed?
If you are afraid of globally scoped variables you could use an overly complicated Singleton class or some kind of static variable for this, but here is the basic idea:
function main() {
global $commit_flag = false;
start_transaction();
doEverything();
if ($commit_flag) {
commit_transaction();
} else {
rollback_transaction();
}
}
function doEverything() {
global $commit_flag;
try {
/* do some stuff that may cause errors */
$commit_flag = true;
} catch { ... }
}
You can handle mysql exceptions using DECLARE ... HANDLER Syntax, but I don't find it proper to try to handle them through PHP !
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
INSERT INTO Users ( user_id ) VALUES('1');
INSERT INTO Users ( user_id ) VALUES('2');
COMMIT;
END;
More information can be found here :
http://dev.mysql.com/doc/refman/5.5/en/declare-handler.html

Specific error code from stored procedure with PDO (in Zend Framework 2)

I am trying to get up and running with stored procedures in Zend Framework 2. I tried to return an error code with an out parameter in the stored procedure, but I have been unable to make that work. Then I thought that when an error occurred, I could just catch an exception in PHP. The problem is that I can't seem to get access to the specific error code - only a general one (e.g. 23000 - integrity constraint violation). Here is an example of what I want to do (or similar):
try {
$result = $this->dbAdapter->query('CALL sp_register_user(?, ?)', array('username', 'password'));
}
catch (\Exception $e) {
switch ($e->getCode()) {
case 1062:
// Duplicate entry
break;
case 1452:
// Cannot add or update a child row
break;
}
}
That is, I would like to be able to check exactly which error occurred. The problem, though, is that the exception that is thrown has an error code of 23000 and not one of the above.
An InvalidQueryException is thrown in Zend\Db\Adapter\Driver\Pdo\Statement on line 220:
try {
$this->resource->execute();
} catch (\PDOException $e) {
throw new Exception\InvalidQueryException('Statement could not be executed', null, $e);
}
The PDOException here contains an error code of 23000 in my case. The null parameter is the error code. So in my catch block, I will actually be catching an InvalidQueryException with an error code of 0, which is not all that useful. This exception does provide me access to previous exceptions (the last parameter above), which would be the PDOException, like this:
// try block omitted
catch (InvalidQueryException $e) {
$previous_exception_error_code = $e->getPrevious()->getCode();
}
On duplicate entry (1062), the error code is 23000. For a "cannot add or update a child row" (1452), it would also be 23000. Therefore I am not sure how I can distinguish between them in my application. What if I wanted to present different error messages to the user for the two errors? The exception message is: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'user' for key 'username'. This is a string, though, so searching for the error code would just be too hacky. I am hoping to be able to still abstract away from the fact that I am using PDO as the concrete implementation.
In the end, this seems more about PDO and MySQL than ZF2. I am not really sure where to go from here, so any ideas on how I can distinguish between the error codes would be much appreciated. That is, to know when an error 1062 or 1452 occurred rather than just 23000.
You want to switch on $e->errorInfo[1], which is the driver-specific error code (instead of the standardised SQLSTATE value).
may be it will help some body-> sometimes the try catch block is not efficient read here the comments
and here the solution.

handling double username in mysql and php

I'm writing my own implementation of an API. One of the resources of this API is /user, I can POST a JSON string to this, parse it and send it to a UserService which has an addUser method. Now obviously I can't allow two users to have the same username. This is what I have to so far.
protected function handlePost() {
$user = json_decode($this->getRequest()->getRequestData(), true);
try {
$createdUserId = $this->userService->addUser(
$user['username'],
$user['password'],
$user['typeId'],
$user['companyId']
);
} catch (PDOException $e) {
$this->getResponse()->setStatus(102);
}
if ($createdUserId)
$this->getResponse()->setStatus(101);
$this->getResponse()->sendResponse();
}
This function is called after the HTTP request has been parsed. As you can see, I get the $user object in an associative array. I then use these values as parameters to the $this->userService->addUser method.
This is the addUser() method:
public function addUser($username, $password, $type, $companyId) {
$sth = $this->dbh->prepare('INSERT INTO app_user
(username, password, typeId, companyId)
VALUES (?, ?, ?, ?)');
$userAdded = $sth->execute(array($username, md5($password), $type, $companyId));
return ($userAdded)
? $this->dbh->lastInsertId()
: null;
}
Now if the $sth->execute fails for some reason, I return null (and neither status code 101 or 102 will be returned, I'm aware of this). But if a username already exists PDO throws an exception, which I catch in my handlePost() method above.
My problem with this is of course that a username which has already been taken is not exceptional and should not be handled this way but I don't really know how I should handle it?
It must remain possible for me to be able to distinguish between a unique constraint being triggered and some other (maybe concurrency issue) being triggered so I can send the appropriate status code back to the consumer of the API.
An obvious solution would be to check if the username exists in a a separate query but for obvious reasons I would prefer to keep the calls to the database limited.
How can I solve this elegantly? If I do have to use exceptions, is there a list of specific PDO exceptions so that I don't have to use the generic PDOException?
Are you concerned enough with performance (i.e. minimizing queries against the database) that you wouldn't just check the existence of the user name in the database before trying to do an insert? You might find this easier logic to follow than trying to catch the Exception and figure out if there really is an exception or not based on the number of rows effected with the last query or similar.
I would also add that if you want to make it not an exception you could add ON DUPLICATE KEY logic to your insert.
PDO should not send an exception for an integrity constraint violation if your not using the ERRMODE_EXCEPTION in your PDO constructor.
In that case, you must call the method PDOStatement::errorCode() or PDOStatement::errorInfo()
to get the details of the error and see if it's an integrity constraint violation or a real problem.

How to properly handle errors from PDO fetch()?

The doc for error handling for fetch() seems unclear and I haven't found an answer anywhere after a lot of searching. A common usage model for fetch as shown in the doc is:
while ( $row = $stmt->fetch() ) {
// do something with $row
}
The PHP doc at http://www.php.net/manual/en/pdostatement.fetch.php says:
In all cases, FALSE is returned on failure.
What does "failure" mean? An error? A failure to fetch more rows? My question is: when fetch() returns FALSE, what is best practice to detect errors? It seems like after the loop I need to distinguish between an error case and the "no more rows" case.
Should I call $stmt->errorCode() and see if it's '00000' ?
To handle query errors (sql errors not occurs on fetch) and more generaly PDO error you should try using PDO::ERRMODE_EXCEPTION.
Moreover the PdoStatement returned by query() implement Traversable , so you can skip the use of fetch() for simple query :
try {
$db = new PDO('sqlite:mydb.db');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Set Errorhandling to Exception
foreach($db->query("SELECT...") as $row)
{
//Do domething
}
$db = null; // "Disconnect"
}
catch (PDOException $err) {
//Handling query/error
}
An execption will be thrown if query encounter an error (not fetch()).
Fetch will always return false when it reach the end of your result set.
fetch() will return false when there's no more data to be fetched. There could also be other case where there truly is a failure, e.g. mysql died or the connection was lost while doing the fetch loop. But generally speaking, that sort of "real" failure is fairly rare, and you'll most often get "false because there's no more data available".
If you're truly paranoid about writing error handling, then by all means, put an sql error code check after the while loop to catch the cases when something really did blow up.

Error handling in PHP and Codeigniter. How to do it? Avoiding recursive code

I am utilizing Codeigniter.
I have developed a variety of features, and they work perfectly if used as intended.
One such script, a whois script checks the owner of a domain name.
If however the user types in an invalid domain name, all sorts of errors are being thrown up here there and everywhere.
For example, if a user types in stack.-com, this is of course not a valid domain. Thus when i call my helper which does the query, no result is return and a variety of errors are returned. There are also errors when i try to display an empty array to the user.
My question relates to errors.
I could use preg_match and check if the domain is valid. If not i set an error variable which i intend to output to the user.
However before my controller gets to the if else statement which decides whether to show the error page or the results page, the program is running queries, and accessing methods to get the data which were there no errors would get the data to pass to the result view.
I.E I know there is an error, but still lots of other errors are being shown because an invalid item is being passed to my other scripts.
With Codeigniter, and the MVC setup, what is the best way of catching the error and displaying it to the user without having to use exceptions which do the same thing over and over again?
Thanks
EDIT WITH IDEA
try
{
$this->load->model('whois');
$this->whois->process('domain.-com');
}
catch
{
$this->load->view('errors',$errordata);
$this->load->view('footer');
die;
}
$this->load->view('normal_content');
$this->load->view('footer');
Is this the suggested setup for using exceptions with codeigniter? within my model, the function will throw exceptions if there is a problem. The catch statement will then display them and die, thus not showing the content.. It does not seem right..?
Here's the way I usually handle this:
Post your form back to the same route
If there are errors, show the form again, with an error state
If everything passes, redirect NOW to the next step / success state
Sample code:
<?php
...
public function form()
{
if (strtoupper($this->input->server('REQUEST_METHOD')) == 'POST')
{
try {
// handle all your validation here
redirect('success_route');
exit;
}
catch (Exception $e)
{
// this could get a little fancier, but a simple solution is to pass the exception
// directly to the view. you could also load the `errors` view here, but return the
// content to a variable and pass to your full view
$this->load->vars('exception', $e);
}
}
$this->load->view('normal_content');
$this->load->view('footer');
}
...
You have to do as follows
1) database.php : $db['default']['db_debug'] = FALSE;
2) in your modal file
try {
$query_str = "SELECT * FROM `pro_property` WHERE username = '".$username."'";
$result = $this->db->query($query_str);
if (!$result)
{
throw new Exception('error in query');
return false;
}
return $result;
} catch (Exception $e) {
print_r($e);
}

Categories