PHP try/catch with SQL. All or Nothing - php

I need to make changes in two databases at the same time, but I don't want either of them to go through if either fails.
This block of code attempts to update the password for a user in two separate databases. The first query goes, then the database is switched, then the second query goes. The try/catch block will fire if there is an error, however if the first query is successful and the second query is not, the first query still goes through despite the exception being thrown and caught. Is try/catch not the right way to approach this?
Thanks for any input!
try {
$new_hash = password_encrypt($new_password);
$query = "UPDATE user SET ";
$query .= "user.hashedPassword = '{$new_hash}' ";
$query .= "WHERE user.userID = '{$user_id}' ";
if(!mysqli_query($connection, $query)) {
throw new Exception('Change Unsuccessful.');
}
if(!mysqli_select_db($connection, 'sub_' . $user['username'] . '_db')) {
throw new Exception('Change Unsuccessful. Switch Error.');
}
$query = "UPDATE user SET ";
$query .= "user.hashedPassword = '{$new_hash}' ";
$query .= "WHERE user.userID = '{$user_id}' ";
if(!mysqli_query($connection, $query)) {
throw new Exception('Change Unsuccessful.');
}
$_SESSION['message'] = 'Password Changed.';
$_SESSION['messageType'] = 'success';
redirect_to("/main.php");
} catch(Exception $e) {
$_SESSION['message'] = $e->getMessage();
$_SESSION['messageType'] = 'danger';
redirect_to("/main.php");
}

I know this is old, but I came across it via a DuckDuckGo search first. Here is what I think to be the right answer;
Use SQL transactions.
In your case, you must start the transactions for both databases, and if either database throws an exception, roll them both back.
At the end of the day, transaction allows you to actually try and complete or undo the command.

you can try to add
if (!$mysqli->query("query"))
{
printf("Error: %s\n", $mysqli->error);
}
and see if there's any error

Related

mysqli multi query separation

I know you can run multiple queries with mysqli_multi_query but I have a problem :
For a registration page I want to perform 2 checks and then an insert :
Check1 = Does username exist?
Check2 = Email already been used?
If both checks are negative then do the insert query.
But how can I sepperate the errors ?
when a username exists it has to return 'Username already exists'
if email is already been used it has to return 'Email already in use'
Here is my code:
$sql = "check username query; ";
$sql .= "check email query; ";
$sql .= "Insert query";
if (mysqli_multi_query($conn,$sql)) {
do
{
if ($result=mysqli_store_result($conn)) {
while ($row=mysqli_fetch_row($result)) {
if (mysqli_num_rows($result) > 0) {
//DO STUFF
//Here do i need to return if the username or email exists or not.
};
mysqli_free_result($result);
};
} else {
echo 'Query fout!';
};
while (mysqli_next_result($con));
};
mysqli_close($con);
You do not write a ; after an if statement or a loop. Furthermore, your do {} while(); loop is not in the correct format.
Correct Format:
$sql = "check username query; ";
$sql .= "check email query; ";
$sql .= "Insert query";
if (mysqli_multi_query($conn,$sql)) {
do {
if ($result = mysqli_store_result($conn)) {
while ($row = mysqli_fetch_row($result)) {
if (mysqli_num_rows($result) > 0) {
//DO STUFF
//return if the username or email exists
}
mysqli_free_result($result);
}
}
else {
echo 'Query fout!';
}
} while (mysqli_next_result($con));
mysqli_close($con);
}
mysqli_multi_query() is not the tool to use for this.
That mysql[i] requires you to jump through hoops to execute more than one DML operation per call provides a lot of protection against sql injection (but it still falls a long way of a full solution for such attacks). There is no need to implement these as a multi-statement-query.
There is a performance and scalability benefit to limiting the number of round trips to the database - but you can do this:
SELECT SUM(IF('$username'=user.username, 1, 0)) AS usernames,
SUM(IF('$email'=user.email, 1, 0)) as emails
FROM users
WHERE username='$username' OR email='$email'
to get the results in a single query.
It would be even more efficient to not bother with a SELECT, but instead add a unique index to each of the attributes in the database then handle a duplicate record error if the INSERT fails although this does not make for such a nice user experience. OTOH it does prevent enumeration attacks against the database.

MySQL syntax error within PHP app, though works in MySQL terminal

EDIT: 'solution' in my comment below
I am doing an AJAX POST to my back-end PHP where form data gets sent to a dedicated CRUD php form and is used to create a record.
The SQL syntax is such that I need 2 commands. Therefore, I use a BEGIN TRANSACTION; statements; COMMIT; structure, thus:
START TRANSACTION;
INSERT INTO tblTask (subject, dateOpened, priority) VALUES
('test 1', '2017-02-22 07:09:33', 3);
INSERT INTO tblTaskContent (idTask, dateEntered, text) VALUES
(LAST_INSERT_ID(), '2017-02-22 07:09:33', 'fdsa');
COMMIT;
*formatted for clarity, though all statements separated by ; and a following space.
That MySQL statement is spat out to the web console via a PHP echo statement, so that's what the program is outputting verbatim.
Here's the PHP:
if($action == 1) //CREATE
{
if($taskVarArr['subject'] && $taskVarArr['priority'] && $taskVarArr['content']) //can create the initial record
{
if(ReadTask($taskVarArr)) //task exists, consider updating existing record
{
echo "Row exists!"; //TODO IS DEBUG
return http_response_code(400);
die();
}
else if(CreateTask($taskVarArr)) //new record, insert
{
return http_response_code(200);
die();
}
}
//if this reached, bad request
return http_response_code(400);
die();
}
elseif($action == 2) //READ ...not used yet
{
}
EDIT: Here are the PHP functions CreateTask and ReadTask:
// Create - CRUD
function CreateTask($taskVarArr) //sorry for your eyes
{
$conn = OpenConnection();
$workingDate = date('Y-m-d h:i:s');
$sql = "START TRANSACTION; ";
$sql .= "INSERT INTO tblTask (subject, dateOpened, priority) VALUES ".
"('".$taskVarArr['subject']."', '$workingDate', ".$taskVarArr['priority']."); "; //TODO below this breaks
$sql .= "INSERT INTO tblTaskContent (idTask, dateEntered, text) VALUES ".
"(LAST_INSERT_ID(), '$workingDate', '".$taskVarArr['content']."'); "; //LAST_INSERT_ID() grabs last autonumber
$sql .= "COMMIT;";
//TODO IS DEBUG
echo $sql;
//TODO IS DEBUG
$result = mysqli_query($conn, $sql);
if($result)
{
return true;
}
return false;
}
// Read - CRUD; Only checks if record exists
function ReadTask($taskVarArr)
{
$conn = OpenConnection();
$sql = "SELECT idTask from tblTask WHERE subject = '".$taskVarArr['subject']."';";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
$row = mysqli_fetch_assoc($result);
return $row['idTask'];
}
return false;
}
I get a 400 error on creation since ReadTask doesn't find the record and CreateTask doesn't complete successfully. However, when I paste the same statement into my MySQL PuTTY window without additional formatting, it accepts it just fine. After trying to run again with the same information, the program detects the row(s) exist and echoes "Row exists!" as it's supposed to. I have verified all credentials are correct and the connection is being opened as expected.
Is LAST_INSERT_ID() limited to PDO or a specific implementation within PHP? I thought MySQL was meant to interpret that.
If you're curious, I've split up tblTask and its content in tblTaskContent because I want the user to be able to add updates to their task as they go along. There's a tblTask id FK and a timestamp in tblTaskContent to keep the data linked and sortable based on update entry time.
Fixed.
// Create - CRUD
function CreateTask($taskVarArr) //sorry for your eyes
{
$conn = OpenConnection();
$workingDate = date('Y-m-d h:i:s');
$sql = "INSERT INTO tblTask (subject, dateOpened, priority) VALUES ".
"('".$taskVarArr['subject']."', '$workingDate', ".$taskVarArr['priority']."); ";
if(mysqli_query($conn, $sql))
{
$sql = "INSERT INTO tblTaskContent (idTask, dateEntered, text) VALUES ".
"(LAST_INSERT_ID(), '$workingDate', '".$taskVarArr['content']."'); "; //LAST_INSERT_ID() grabs last autonumber
if(mysqli_query($conn, $sql))
{
return true;
}
}
return false;
}
That's a dumb solution. I was hoping utilizing both mysqli_autocommit and mysqli_commit after the mysqli_querys would work, but it don't. It seems if I want a transaction I'll have to (learn how to) use PDO.

INTRO-LVL PROGRAMMER: Verify & Execute Guidance in PHP/MySQL

I'm a non-CIS major taking an intro programming classes for a minor through my university. I've been able to successfully code most of the PHP files I need but have been getting hung up over how to perform two functions within the same document. Hopefully you can help.
Within the website, I want to be able to first use MySQL to check a table, called User (where a user is initially registered by the site) to verify that they are in fact registered and that the credentials they provided are correct, and then execute an query to add them to another table.
I've tried mysqli_multi_query to no avail and am just generally inexperienced and unsure of my options as far as functions go.
I have included the code below but be aware that it is a mess as I've attempted several different things before I decided to get some help
<?php
session_start();
require_once("config.php");
$GroupDesc = $_GET["GroupDesc"];
$LeaderID = $_GET["LeaderID"];
$URL = $_GET["URL"];
$Email=$_GET["Email"];
$con = mysqli_connect("$SERVER","$USERID","$DBPASSWORD","$DATABASE");
$query2= "INSERT INTO FA15_1052_tuf02984.WebsiteGroups (ID, Description, LeaderID, URL, LeaderEmail) VALUES ('$GroupDesc', '$LeaderID', '$URL', '$Email');";
/* Here I want to perform the first query or $query1 which checks if the
user exists in MySQL and the info submitted in form is same */
$query1= "SELECT * from USER where LeaderID = '$ID' and Email = '$Email';";
if ($status = mysqli_query($con, $query1)) {
} else {
print "Some of the data you provided didn't match our records. Please contact the webmaster.".mysqli_error($con)." <br>";
$_SESSION["RegState"]= -11;
$_SESSION["ErrorMsg"]= "Database insertion failed due to inconsistent data: ".mysqli_error($con);
header("Location:../index.php");
die();
}
/* How do I tell the file to move onto the next query, which is $query2?
if ($query2) {
$query = "INSERT INTO FA15_1052_tuf02984.WebsiteGroups (ID, Description, LeaderID, URL, LeaderEmail)
VALUES ('$GroupDesc', '$LeaderUID', '$URL', '$Email');";
} */
} else {
print "Membership update failed. Please contact webmaster.".mysqli_error($con)." <br>";
$_SESSION["RegState"]= -11; // 0: Not Registered, 1: Register, -1: Error
$_SESSION["ErrorMsg"]= "Database Insert failed: ".mysqli_error($con);
header("Location:../index.php");
die();
}
There are a few points where your code can be rearranged to make the logic easier to follow. (Don't worry; this is just stuff that comes with experience.) I'll include some comments within the following code to explain what I've done.
<?php
session_start();
require_once("config.php");
$GroupDesc = $_GET["GroupDesc"];
$LeaderID = $_GET["LeaderID"];
$URL = $_GET["URL"];
$Email=$_GET["Email"];
// mysqli_connect is deprecated; the preferred syntax is
$con = new mysqli("$SERVER","$USERID","$DBPASSWORD","$DATABASE");
$query1= "SELECT * from USER where LeaderID = '$ID' and Email = '$Email';";
$result = mysqli_query($con, $query1);
// I personally prefer the following opening-brace style; I just find it
// easier to read. You can use the other style if you want; just do it
// consistently.
if ($result)
{
$row = mysqli_fetch_assoc($result);
if($row)
{
if (($row['ID'] != $LeaderID) or ($row['Email'] != $Email))
{
// Handle the error first, and exit immediately
print "Some of the data you provided didn't match our records. Please contact the webmaster.".mysqli_error($con)." <br>";
$_SESSION["RegState"]= -11;
$_SESSION["ErrorMsg"]= "Database Insert failed due to inconsistent data: ".mysqli_error($con);
header("Location:../index.php");
die();
}
else
{
// If the query succeeded, fall through to the code that processes it
$query = "INSERT INTO FA15_1052_tuf02984.WebsiteGroups (ID, Description, LeaderID, URL, LeaderEmail)
VALUES ('$GroupDesc', '$LeaderUID', '$URL', '$Email');";
$status = mysqli_query($con, $query);
if ($status)
{
// membership has been updated
$_SESSION["RegState"]=9.5; // 0: Not Registered, 1: Register, -1: Error
$message="This is confirmation that you the group you lead has been added to our database.
Your group's ID in our database is "$GID". Please keep this in your records as you will need it to make changes.
If this was done in error, please contact the webmaster at tuf02984webmaster#website.com";
$headers = 'From: tuf02984webmaster#example.com'."\r\n".
'Reply-To: tuf02984webmaster#example.com'. "\r\n".
'X-Mailer: PHP/' . phpversion();
mail($Email, "You are a group leader!", $message, $headers);
header("Location:../index.php");
// die();
// You only use die() to return from an error state.
// Calling die() creates an entry in the server's error log file.
// For a successful completion, use
return;
}
}
}
}
// If we get here, then something has gone wrong which we haven't already handled
print "Membership update failed. Please contact webmaster.".mysqli_error($con)." <br>";
$_SESSION["RegState"]= -11; // 0: Not Registered, 1: Register, -1: Error
$_SESSION["ErrorMsg"]= "Database Insert failed: ".mysqli_error($con);
header("Location:../index.php");
die();
?>
The basic idiom is: Do something, handle the specific error, handle success, do something else, etc., and finally handle any errors that can come from multiple points. If anything is unclear, just ask and I'll edit into my answer.
I haven't covered prepared statements here. Prepared statements are the preferred way to perform non-trivial queries; they help to resist SQL injection attacks as well as simplify type-matching, quoting and escaping of special characters.

mysql Duplicate error handling

I'm trying to use PHP to enter data from a form. When I try to enter duplicate data a bad message pops like
Something went wrong with this:
INSERT INTO customer VALUES('jamie9422','Jamie Lannister','sept of baelor','jamie#cersei.com',9422222222,0) Duplicate entry 'jamie9422' for key 'PRIMARY' "
Instead, I want to display a clean error message. How can I do that. Here's my code I've written so far...
<?php
include_once "dbConnect.php";
$connection=connectDB();
if(!$connection)
{
die("Couldn't connect to the database");
}
$tempEmail = strpos("{$_POST["email"]}","#");
$customer_id=substr("{$_POST["email"]}",0,$tempEmail).substr("{$_POST["phone"]}",0,4);
//$result=mysqli_query($connection,"select customer_id from customer where customer_id='$customer_id' ");
//echo "customer_id is".$result;
$query = "SELECT * FROM CUSTOMER WHERE CUSTOMER_ID='$customer_id'";
$customer_idcip = $customer_id-1;
echo $customer_idcip;
if ( mysql_query($query)) {
echo "It seems that user is already registered";
} else {
$command = "INSERT INTO customer VALUES('{$customer_id}','{$_POST["name"]}','{$_POST["address"]}','{$_POST["email"]}',{$_POST["phone"]},0)";
$res =$connection->query($command);
if(!$res){
die("<br>Something went wrong with this:{$command}\n{$connection->error}");
}
echo "Welcome ".$_POST["name"]." \nCongratulations on successful Registration. Refill your Wallet here";
//$cutomerRetrival = mysql_query("select from customer where customer_id='$customer_id'");
echo "<br>Please note your customer ID :".$customer_id;
}
/*if($result)
{
echo "Query Fired";
$dupentry = mysqli_num_rows($result);
if($dupentry==1)
{
echo "You are already Registered";
exit;
}
}*/
?>
The error code (number) is 1022.
You can e.g. define a constant for that (so that somebody else in x months has a chance to understand the code) like
define('ER_DUP_KEY', 1022);
and then do something like
if(!$res){
if ( <error code>==ER_DUP_KEY ) {
handleDuplicateEntryError();
}
else {
die("<br>Something went wrong with this:{$command}\n{$connection->error}");
}
}
since I don't know how $res =$connection->query($command); works (and what $connection is I can't tell you exactly how to implement <error code>==ER_DUP_KEY, could be by using mysql_errno.
But it seems to be somehow intermingled with mysql_query($query), i.e. the old, deprecated mysql_* extension and some custom class. You might want to fix that first.... ;-)
see http://docs.php.net/manual/en/mysqlinfo.api.choosing.php
Your code doesn't check for existing record properly
Change
if (mysql_query($query)) {
echo "It seems that user is already registered";
}
to
$result = mysql_query($query);
if (mysql_num_rows($result)) {
echo "It seems that user is already registered";
}
Also, PLEASE do not use $_POST variables without escaping them first, use something like mysql_real_escape_string() to escape each variable passed from the user, otherwise your website will be hacked really fast with SQL Injection.
Make some update into your and then try to get error message 'customer already registered.'
$query = "SELECT * FROM CUSTOMER WHERE CUSTOMER_ID='$customer_id'";
$res= mysql_query($query);
$customer_count = mysql_num_rows($res);
$customer_idcip = $customer_id-1;
echo $customer_idcip;
if ( $customer_count > 0 ) {
echo "It seems that user is already registered";
} else {
...................................
Thank you all.
Actually I was using mysqli API in my connectDB.php file..
Hence I needed to call functions on mysqli.
Instead I was calling mysql. i.e I was creating a new connection, thus the query wasn't getting fired at all.
Changed to mysqli->query($result) that is object oriented style
and it worked fine....
Use Try Catch instead.
try{
$res =$connection->query($command);
}catch(Exception $e){
die( "Write your error appropriate message here");
}

Duplicate check before adding into database

I have a code which kinda works, but not really i can't figure out why, what im trying to do is check inside the database if the URL is already there, if it is let the user know, if its not the go ahead and add it.
The code also makes sure that the field is not empty. However it seems like it checks to see if the url is already there, but if its not adding to the database anymore. Also the duplicate check seems like sometimes it works sometimes it doesn't so its kinda buggy. Any pointers would be great. Thank you.
if(isset($_GET['site_url']) ){
$url= $_GET['site_url'];
$dupe = mysql_query("SELECT * FROM $tbl_name WHERE URL='$url'");
$num_rows = mysql_num_rows($dupe);
if ($num_rows) {
echo 'Error! Already on our database!';
}
else {
$insertSite_sql = "INSERT INTO $tbl_name (URL) VALUES('$url')";
echo $url;
echo ' added to the database!';
}
}
else {
echo 'Error! Please fill all fileds!';
}
Instead of checking on the PHP side, you should make the field in MySQL UNIQUE. This way there is uniqueness checking on the database level (which will probably be much more efficient).
ALTER TABLE tbl ADD UNIQUE(URL);
Take note here that when a duplicate is INSERTed MySQL will complain. You should listen for errors returned by MySQL. With your current functions you should check if mysql_query() returns false and examine mysql_error(). However, you should really be using PDO. That way you can do:
try {
$db = new PDO('mysql:host=localhost;db=dbname', $user, $pass);
$stmt = $db->query('INSERT INTO tbl (URL) VALUES (:url)');
$stmt->execute(array(':url' => $url));
} catch (PDOException $e) {
if($e->getCode() == 1169) { //This is the code for a duplicate
// Handle duplicate
echo 'Error! Already in our database!';
}
}
Also, it is very important that you have a PRIMARY KEY in your table. You should really add one. There are a lot of reasons for it. You could do that with:
ALTER TABLE tbl ADD Id INT;
ALTER TABLE tbl ADD PRIMARY KEY(Id);
You should take PhpMyCoder's advice on the UNIQUE field type.
Also, you're not printing any errors.
Make sure you have or die (mysql_error()); at the end of your mysql_* function(s) to print errors.
You also shouldn't even be using mysql_* functions. Take a look at PDO or MySQLi instead.
You're also not executing the insert query...
Try this code:
if(isset($_GET['site_url']) ){
$url= $_GET['site_url'];
$dupe = mysql_query("SELECT * FROM $tbl_name WHERE URL='$url'") or die (mysql_error());
$num_rows = mysql_num_rows($dupe);
if ($num_rows > 0) {
echo 'Error! Already on our database!';
}
else {
$insertSite_sql = "INSERT INTO $tbl_name (URL) VALUES('$url')";
mysql_query($insertSite_sql) or die (mysql_error());
echo $url;
echo ' added to the database!';
}
}
else {
echo 'Error! Please fill all fileds!';
}
As PhpMyCoder said, you should add a unique index to the table.
To add to his answer, here is how you can do what you want to do with only one query.
After you add the unique index, if you try to "INSERT INTO" and it result in a duplicate, MySQL will produce an error.
You can use mysql_errno() to find out if there was a duplicate entry and tell the user.
e.g.
$sql = "INSERT INTO $tbl_name (URL) VALUES('$url')";
$result = mysql_query($sql);
if($result === false) {
if(mysql_errno() == $duplicate_key_error) {
echo 'Error! Already in our database!';
} else {
echo 'An error has occurred. MySQL said: ' . mysql_error();
}
}
mysql_error() will return the mysql error in plain english.
mysql_errno() returns just the numeric error code. So set $duplicate_key_error to whatever the code is (I don't know it off the top of my head) and you are all set.
Also note that you don't want to print any specific system errors to users in production. You don't want hackers to get all kinds of information about your server. You would only be printing MySQL errors in testing or in non-public programs.
ALSO! Important, the mysql functions are deprecated. If you go to any of their pages ( e.g. http://php.net/manual/en/function.mysql-errno.php) you will see recommendations for better alternatives. You would probably want to use PDO.
Anyone who wants to edit my answer to change mysql to PDO or add the PDO version, go ahead.

Categories