Action: creating table record that is a duplicate on a unique key.
Good: The unique key is working because the duplicate record is not created.
Bad: The mysqli functions that I am using in PHP are not failing in the way that I expect.
Meta: I have some PHP (running 5.3.6) that handles creating new records in a Mysql (5.5.9) table from AJAX POST requests. Authentication and input scrubbing and validation is handled outside of the scope of the code that I am sharing. The respond function also handles printing out the response in a valid format. The table I am inserting into has an auto incrementing id column and a unique name column.
// connect to server
$mysqli = new mysqli("localhost", "dbuser", 'dbpassword', "database");
if(mysqli_connect_errno()) {
$response["message"] = "unable to connect to the MySQL server at 'localhost' as 'dbuser' using a password or select the database 'database' because ".mysqli_connect_errno();
respond($response);
}
// build and run query
if ($stmt = $mysqli -> prepare("INSERT INTO `database`.`table` (`id`, `name`) VALUES (NULL, ?);")) {
$stmt -> bind_param("s", $inputs["name"]);
$success = $stmt -> execute();
if ($success) {
$response["status"] = "success";
$response["id"] = $mysqli -> insert_id;
respond($response);
} else {
$response["message"] = "failed to insert because ".$stmt -> error;
}
$stmt -> close();
} else {
$response["message"] = "unable to build query because ".$mysqli -> error;
respond($response);
}
$mysqli -> close();
This works as expected when inserting a new unique name but doesn’t return anything when attempting to insert a new duplicate name. I expected the
$stmt -> execute();
to fail so I would return a response explaining the error. This code doesn’t do that. The only way I have found to return that the create failed because of a duplicate is to add 2 lines of code before the close statement like this:
// connect to server
$mysqli = new mysqli("localhost", "dbuser", 'dbpassword', "database");
if(mysqli_connect_errno()) {
$response["message"] = "unable to connect to the MySQL server at 'localhost' as 'dbuser' using a password or select the database 'database' because ".mysqli_connect_errno();
respond($response);
}
// build and run query
if ($stmt = $mysqli -> prepare("INSERT INTO `database`.`table` (`id`, `name`) VALUES (NULL, ?);")) {
$stmt -> bind_param("s", $inputs["name"]);
$success = $stmt -> execute();
if ($success) {
$response["status"] = "success";
$response["id"] = $mysqli -> insert_id;
respond($response);
} else {
$response["message"] = "failed to insert because ".$stmt -> error;
}
$stmt -> close();
} else {
$response["message"] = "unable to build query because ".$mysqli -> error;
respond($response);
}
$response["message"] = "Duplicate";
respond($response);
$mysqli -> close();
The thing I don’t understand is when the query runs as expected, the duplicate response is not returned. When the query fails because it is trying to insert a duplicate on the unique key, this returns the duplicate response.
Question: Why does a successful insert not run the respond function at the end of the code block?
Question: Why does a failed insert because of attempting to insert a duplicate on a unique key not throw an exception or fail as I expected?
I have also tried wrapping the entire thing in a try catch block but it didn’t help any because it’s not throwing any exceptions. If you could shed any light on this matter I would greatly appreciate it.
Question: Why does a successful insert not run the respond function at the end of the code block?
I assume your
respondmethod has anexitordiein it somewhere that halts execution.Question: Why does a failed insert because of attempting to insert a duplicate on a unique key not throw an exception or fail as I expected?
I think it’s to do with the logic in your
ifstatements.If your procedure ever gets into the else statement
no response is ever sent (the
respondfunction is never called). The reason your second example works is because the code reaches that point every time (because its outside the lastelsestatement. If you move a call torespondafter the line above you might find it returns the message as expected. Full code might look like the following: