What is the proper way to handle error on duplicate entries for PHP/MySQL?
The code below is not working even tho the code 1062 is the right code for duplicate entries. Should i use DBO instead here? Unfortunately i'm not familiar yet.
<?php
try {
mysql_query('INSERT INTO TP2_ORGANISME VALUES (A0A0, Equiterre, 1,
/photos/equiterre_logo.png, Steve Guilbault, steve#equiterre.org');
} catch {
if (mysql_errno() == 1062)
echo 'Duplicate key entry';
}
?>
I'm just looking for a simple try catch or anything that can just let me print a message to the user who entered a duplicate that he has to enter another value for the primary key.
thanks
mysql_query() does not throw exceptions (and only exceptions can be caught). mysqli_ and PDO can, if you enable it to. mysql_ is also very obsolete (and has been for many years already), so use PDO or MySQLi with exception-mode enabled, and it'll work. Both these APIs support prepared statements, so you should use that too if you start inputting variables in your query.
For MySQLi, you need mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); before the connection, and for PDO you need $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
MySQLi example
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli($hostname, $username, $password, $databasename);
$mysqli->set_charset("utf8");
try {
$mysqli->query("INSERT INTO TP2_ORGANISME
VALUES ('A0A0', 'Equiterre', 1, '/photos/equiterre_logo.png', 'Steve Guilbault', 'steve#equiterre.org')");
} catch (mysqli_sql_exception $e) {
if ($e->getCode() == 1062) {
// Duplicate user
} else {
throw $e;// in case it's any other error
}
}
PDO example
$pdo = new PDO("mysql:host=$hostname;dbname=$databasename;charset=utf8", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Enables exception mode
try {
$pdo->query("INSERT INTO TP2_ORGANISME
VALUES ('A0A0', 'Equiterre', 1, '/photos/equiterre_logo.png', 'Steve Guilbault', 'steve#equiterre.org')");
} catch (PDOException $e) {
if ($e->getCode() == 1062) {
// Duplicate user
} else {
throw $e;// in case it's any other error
}
}
MySQLi exception class and mysqli_report()
PDO exception class and PDO::setAttribute()
When you start introducing variables into your queries, look into using a prepared statement (How can I prevent SQL injection in PHP?).
I found this block of code really helped capture the 1062 error, and then redirect from the signup page to the login page, if the error happened. Tested it, and it all works.
catch (PDOException $e) {
if(str_contains($e, '1062 Duplicate entry')) {
header("Location: login.php");
}
die("Error inserting user details into database: " . $e->getMessage());
}
Related
I have code like this
try {
$pdo->beginTransaction();
$stmt = $pdo->prepare($query1);
$stmt->execute($x);
} catch (Exception $e) {
$pdo->rollBack();
throw->$e;
}
if (condition) {
exit();
}
$x['column1'] = 'string1';
$x['column2'] = 'string2';
$x['column3'] = 'string3';
try {
$stmt = $pdo->prepare($query2);
$stmt->execute($x);
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw->$e;
}
If the if condition succeeded and the code did exit()
Is everything related to the $pdo is safe too or do I add before exit() a $pdo->rollBack();?
Technically you don't.
PHP will close the database connection on exit.
Database will roll back all active transactions on close.
However, it is quite unlikely that such a case will ever happen in your code because right now it is wrong. You have to wrap the entire transaction in a try catch, not just database operations. Otherwise, if an exception will be thrown in your "condition" part, it will break a transaction but won't be caught.
Besides, using exit is a bad practice by itself and amidst a transaction a tenfold.
But if you really really need that (in reality you don't) then do something like
try {
$pdo->beginTransaction();
$stmt = $pdo->prepare($query1);
$stmt->execute($x);
if (condition) {
throw new Exception("Stopped on condition");
}
$x['column1'] = 'string1';
$x['column2'] = 'string2';
$x['column3'] = 'string3';
$stmt = $pdo->prepare($query2);
$stmt->execute($x);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw->$e;
}
From the docs:
When the script ends or when a connection is about to be closed, if you have an outstanding transaction, PDO will automatically roll it back.
https://www.php.net/manual/en/pdo.transactions.php
In my opinion, it's better to run rollback to make sure to other devs that it's not code garbage, someon think about it.
I've got a (example) Oracle Stored Procedure:
CREATE OR REPLACE FUNCTION EXCEPTION_TEST
RETURN NUMBER
AS
BEGIN
raise_application_error(-20500, 'This is the exception text I want to print.');
END;
and I call it in PHP with PDO with the following code:
$statement = $conn->prepare('SELECT exception_test() FROM dual');
$statement->execute();
The call of the function works perfectly fine, but now I want to print the Exception text only.
I read somewhere, that you should not use try and catch with PDO. How can I do this?
You have read that you shouldn't catch an error to report it.
However, if you want to handle it somehow, it's all right to catch it.
Based on the example from my article on handling exception in PDO,
try {
$statement = $conn->prepare('SELECT exception_test() FROM dual');
$statement->execute();
} catch (PDOException $e) {
if ($e->getCode() == 20500 ) {
echo $e->getmessage();
} else {
throw $e;
}
}
Here you are either getting your particular error or re-throwing the exception back to make it handled the usual way
You check the execute response and get the error, for example, like this:
if ($statement->execute() != true) {
echo $statement->errorCode();
echo $statement->errorInfo();
}
You can find more options at the PDO manual.
If, for whatever reason, there is an error in creation of an entry using the mapper I get an error.
I'd like to do a custom notification and fail gracefully like so...
try {
$request->save();
} catch (Exception $e) {
$this->utils->errorNotify($f3,'could not create a request entry',http_build_query($_POST));
return null;
}
is this possible with F3?
\DB\SQL is a subclass of PDO so it can throw catchable PDO exceptions. Since these are disabled by default, you need to enable them first. This can be done in 2 different ways:
at instantiation time, for all transactions:
$db = new \DB\SQL($dsn, $user, $pwd, array( \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION ));
later on in the code, on a per-transaction basis:
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
Once PDO exceptions are enabled, just catch them as other exceptions:
try {
$db->exec('INSERT INTO mytable(id) VALUES(?)','duplicate_id');
} catch(\PDOException $e) {
$err=$e->errorInfo;
//$err[0] contains the error code (23000)
//$err[2] contains the driver specific error message (PRIMARY KEY must be unique)
}
This also works with DB mappers, since they rely on the same DB\SQL class:
$db=new \DB\SQL($dsn,$user,$pwd,array(\PDO::ATTR_ERRMODE=>\PDO::ERRMODE_EXCEPTION));
$mytable=new \DB\SQL\Mapper($db,'mytable');
try {
$mytable->id='duplicate_id';
$mytable->save();//this will throw an exception
} catch(\PDOException $e) {
$err=$e->errorInfo;
echo $err[2];//PRIMARY KEY must be unique
}
I have a database class dbconnect.php, and processform.php. Inside dbconnect.php there is a method for connecting to the database.
If there's an error, how do I throw an exception? Where do I put the try catch block, in the processform.php? People say I shouldn't echo an error directly from inside the class. Here's an example:
<?php
// dbconnect.php
class DbConnect
{
public function open_connection()
{
/* Should I do it like this? */
$this->conn = PDO($dsn, $this->username, $this->password);
if (!$this->conn) {
throw new Exception('Error connecting to the database.');
}
/* Or like this */
try {
$this->conn = PDO($dsn, $this->username, $this->password);
} catch (PDOException $e) {
echo 'Error: ', $e->getMessage(), '<br>';
}
}
?>
// processform.php
<?php
require_once 'dbconnect.php';
$pdo = new DbConnect($host, $username, $password);
try {
$pdo->open_connection();
} catch (PDOException $e) {
echo 'Error connecting to the database.');
}
?>
I really want to learn the correct way of implementing the try catch in my code.
You don't have to throw an exception manually, especially on a successful connect :-)
Instead you need to tell PDO that it needs to throw exceptions when something goes wrong and you can do that when you open your database connection:
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
$this->conn = new PDO($dsn, $this->username, $this->password, $options);
Now you can put everything in try / catch blocks but that is not even necessary; if you don't do that, php will show you unhandled exceptions complete with a stack trace when you don't catch them manually.
And when you decide you want to fine-tune your error handling for your visitors, you can set your own exception handler using set_exception_handler(). That way you can handle everything at one place instead of wrapping different sections in try / catch blocks. Should you prefer that of course.
In my practice, I prefer to catch exception in bottom. I mean, second way in your DbConnect.
You can output error message to error log. And return an error code to front-end. So the front-end knows how to tell users an error occours in a friendly way.
What's more, you can use global error handler such as set_error_handler/set_exception_handler to do this. Redirect to an error page when error occours.
I'm using the following script to use a database using PHP:
try{
$db = new PDO('mysql:host='.$host.';port='.$port.';dbname='.$db, $user, $pass, $options);
}
catch(Exception $e){
$GLOBALS['errors'][] = $e;
}
Now, I want to use this database handle to do a request using this code:
try{
$query = $db->prepare("INSERT INTO users (...) VALUES (...);");
$query->execute(array(
'...' => $...,
'...' => $...
));
}
catch(Exception $e){
$GLOBALS['errors'][] = $e;
}
Here is the problem:
When the connection to the DB is OK, everything works,
When the connection fails but I don't use the DB, I have the $GLOBALS['errors'][] array and the script is still running afterwards,
When the connection to the DB has failed, I get the following fatal error:
Notice: Undefined variable: db in C:\xampp\htdocs[...]\test.php on line 32
Fatal error: Call to a member function prepare() on a non-object in C:\xampp\htdocs[...]\test.php on line 32
Note: Line 32 is the $query = $db->prepare(...) instruction.
That is to say, the script crashes, and the try/catch seems to be useless. Do you know why this second try/catch don't works and how to solve it?
Thanks for the help!
EDIT: There are some really good replies. I've validated one which is not exactly what I wanted to do, but which is probably the best approach.
try/catch blocks only work for thrown exceptions (throw Exception or a subclass of Exception must be called). You cannot catch fatal errors using try/catch.
If your DB connection cannot be established, I would consider it fatal since you probably need your DB to do anything meaningful on the page.
PDO will throw an exception if the connection cannot be established. Your specific problem is that $db is not defined when you try to call a method with it so you get a null pointer (sort of) which is fatal. Rather than jump through if ($db == null) hoops as others are suggesting, you should just fix your code to make sure that $db is either always defined when you need it or have a less fragile way of making sure a DB connection is available in the code that uses it.
If you really want to "catch" fatal errors, use set_error_handler, but this still stops script execution on fatal errors.
In PHP7, we now can using try catch fatal error with simple work
try {
do some thing evil
} catch (Error $e) {
echo 'Now you can catch me!';
}
But usualy, we should avoid using catch Error, because it involve to miss code which is belong to programmer's reponsibility :-)
I will not report what has already been written about testing if $db is empty. Just add that a "clean" solution is to artificially create an exception if the connection to the database failed:
if ($db == NULL) throw new Exception('Connection failed.');
Insert the previous line in the try - catch as follow:
try{
// This line create an exception if $db is empty
if ($db == NULL) throw new Exception('Connection failed.');
$query = $db->prepare("INSERT INTO users (...) VALUES (...);");
$query->execute(array(
'...' => $...,
'...' => $...
));
}
catch(Exception $e){
$GLOBALS['errors'][] = $e;
}
Hope this will help others!
If database connection fails, $db from your first try .. catch block will be null. That's why later you cannot use a member of non-object, in your case $db->prepare(...). Before using this add
if ($db) {
// other try catch statement
}
This will ensure that you have db instance to work with it.
Try adding the following if statement :
if ($db) {
$query = $db->prepare("INSERT INTO users (...) VALUES (...);");
$query->execute(....);
}
else die('Connection lost');
try{
if(!is_null($db))
{
$query = $db->prepare("INSERT INTO users (...) VALUES (...);");
$query->execute(array(
'...' => $...,
'...' => $...
));
}
}
catch(Exception $e){
$GLOBALS['errors'][] = $e;
}