Proper usage of php mysqli autocommit and rollback - php

Having trouble with proper usage of mysqli autocommit. Below are the queries.
Table1 and Table3 are InnoDB while Table2 is MyISAM
Values to Table2 and Table3 are inserted properly but values to Table1 are not being stored.
No errors occur while running the code.
$dbconnect->autocommit(false);
$stmt = $dbconnect->prepare("INSERT INTO `table1`(`col1`,`col2`) VALUES (?,?)");
$stmt->bind_param('ss',$val1,$val2);
$stmt->execute();
$dbconnect->rollback();
$stmt = $dbconnect->prepare("INSERT INTO `table2`(`col1`,`col2`) VALUES (?,?)");
$stmt->bind_param('ss',$val3,$val4);
$stmt->execute();
$dbconnect->rollback();
$stmt = $dbconnect->prepare("INSERT INTO `table3`(`col1`,`col2`) VALUES (?,?)");
$stmt->bind_param('ss',$val5,$val6);
$stmt->execute();
$dbconnect->commit();
When and how do you use autocommit(false) and rollback()?

You use it when you have a series of sql statements that must be performed together to maintain consistency in your database. Think of calling commit as establishing a save point in a game. Anytime you call rollback you undo everything that was done up to the previous commit.
Imagine a situation where you need to save an invoice in your invoice table, details in your invoice_details table and payments in your payments table. To maintain consistency you need to make sure that these are all done or none of them is done. If you where to add the invoice and the details and then there was a failure on inserting the payment then your database is left in an inconsistent state.
Normally this is accomplished using a try/catch block like this:
try {
$dbconnect->autocommit(false);
$stmt = $dbconnect->prepare("INSERT INTO `invoices`(`col1`,`col2`) VALUES (?,?)");
$stmt->bind_param('ss',$val1,$val2);
$stmt->execute();
$stmt = $dbconnect->prepare("INSERT INTO `invoice_details`(`col1`,`col2`) VALUES (?,?)");
$stmt->bind_param('ss',$val3,$val4);
$stmt->execute();
$stmt = $dbconnect->prepare("INSERT INTO `payments`(`col1`,`col2`) VALUES (?,?)");
$stmt->bind_param('ss',$val5,$val6);
$stmt->execute();
$dbconnect->commit();
} catch(Exception $e){
// undo everything that was done in the try block in the case of a failure.
$dbconnect->rollback();
// throw another exception to inform the caller that the insert group failed.
throw new StorageException("I couldn't save the invoice");
}

Related

Transactions with PDO and prep. statements

I need to insert to two tables and I tried transactions. It works well:
$nom = "Nrc";
$contrasenya = "somePassword";
$conn->beginTransaction();
$conn->exec("INSERT INTO usuari (nom, contrasenya)
VALUES ('$nom', '$contrasenya')");
$conn->exec("INSERT INTO well (puntuacio, text)
VALUES ('9', 'some text2')");
$conn->commit();
echo "New records created successfully";
Now I want to introduce prep. statements for security. I am not sure how to do that. This is what I tried. It gives me no error, but it does not insert in any table either:
$nom = "Nrc";
$contrasenya = "somePassword";
$conn->beginTransaction();
$stmt = $conn->prepare("INSERT INTO usuari (nom, contrasenya)
VALUES (:nom, :contrasenya)");
$stmt = $conn->prepare("INSERT INTO well (puntuacio, text)
VALUES ('9', 'some text2')");
$stmt->bindParam(':nom', $nom);
$stmt->bindParam(':contrasenya', $contrasenya);
$conn->commit();
echo "New records created successfully";
There are several issues with your code:
You never execute the statement.
You overwrite your statement ($stmt) with the statement using the values directly. So you don't use the correct prepared statement.
You can use the following code to INSERT the values to the tables:
//start the transaction.
$conn->beginTransaction();
//the variables of the first statement.
$nom = 'Nrc';
$contrasenya = 'somePassword';
//prepare the first statement, bind the values and execute.
$stmt = $conn->prepare("INSERT INTO usuari (nom, contrasenya) VALUES (:nom, :contrasenya)");
$stmt->bindParam(':nom', $nom);
$stmt->bindParam(':contrasenya', $contrasenya); //TODO - use hashing here!
//... or solution without variable.
//$stmt->bindValue(':nom', 'Nrc');
//$stmt->bindValue(':contrasenya', 'somePassword');
$stmt->execute();
//the variables of the second statement.
$puntuacio = '9';
$text = 'some text2';
//prepare the second statement, bind the values and execute.
$stmt = $conn->prepare("INSERT INTO well (puntuacio, text) VALUES (:puntuacio, :text)");
$stmt->bindParam(':puntuacio', $puntuacio);
$stmt->bindParam(':text', $text);
//... or solution without variable.
//$stmt->bindValue(':puntuacio', '9');
//$stmt->bindValue(':text', 'some text2');
$stmt->execute();
//commit all changes of the transaction.
$conn->commit();
Note: As already others mentioned, you should also hash your passwords.
From php.net:
Unlike PDOStatement::bindValue(), the variable is bound as a reference and will only be evaluated at the time that PDOStatement::execute() is called.
For password insertion you should use password() function shiped with PHP.
You shouldn't insert direct data directly in prepare statement, as you did for the first one
$stmt = $conn->prepare("INSERT INTO well (puntuacio, text)
VALUES (:number, :some_text)");
$stmt->bindParam(':number', $num);
$stmt->bindParam(':some_text', $text);
You should execute(); your prepared statement in order to execute your query insertion.
Plus as said previously you overwrite your $stmt variable before you can execute your query.

Multiple prepared statements into many to many relationship table using last id

[Updated code from suggestions]
Currently I have two tables in my database. The second table is a junction table for multiple categories from each unique id from the first table. Nothing too special.
Need to accomplish:
I'd like to use two prepared statements where specifically the second statement gets the ID from the first and loops through. Here's what I've tried:
//set autocommit to off
$conn->autocommit(FALSE);
//first table
$stmt1 = $conn->prepare("INSERT INTO birds (db_category, db_class) VALUES (?, ?)");
$stmt1->bind_param('ss',$_POST['db_category'],$_POST['db_class']);
$stmt1->execute();
//get the inserted ID
$lastID = $stmt1->insert_id;
$stmt1->close();
//second table (many to many)
$stmt2 = $conn->prepare("INSERT INTO birdsBiome (birds_id, biomes_id) VALUES (?, ?)");
$arrayValue = $_POST['biomeCheck'];
foreach ($arrayValue as $arrayInsert) {
$stmt2->bind_param('ii', $lastID, $arrayInsert);
$stmt2->execute();
}
$stmt2->close();
//commit both statements
$conn->commit();
$conn->close();
You can access the last insert id as a property of the mysqli object:
$lastID = $conn->insert_id;
Or alternatively you don't have to store $lastID at all if you access it within your SQL:
$stmt2 = $conn->prepare("INSERT INTO birdsBiome (birds_id, biomes_id)
VALUES (LAST_INSERT_ID(), ?)");
By the way, in your code example above you didn't execute $stmt1, so there will be no last insert id anyway.

prepared INSERT statement to get insert_id to use in a second prepared INSERT statement

Im trying to create my own register form but im having issues with prepared statements.
the idea is to create a prepared statement to insert info into the user table, then from that get the insert_id from the generated content to use in another insert statement
here is a version of my register script
<?php
$returnedId = '';
include "includes/dbconnect.php";
$stmt = $db->prepare("INSERT INTO `users`(`Username`, `Email`, `Password`) VALUES (?, ?, ?)");
$stmt->bind_param('sss', $_POST['username'], $_POST['email'], $_POST['password']);
$stmt->execute();
$returnedId = $stmt->insert_id;
$stmt->close();
echo $returnedId;
$allergystmt = $db->prepare("INSERT INTO 'user_allergy' ('user_id', 'allergy_id') VALUES (?, ?)");
$allergystmt->bind_param('ss', $returnedId, $_POST['check_list']);
$allergystmt->execute();
$allergystmt->close();
header('Location: index.php');
?>
the first prepared statement runs correctly and inserts information into the table, after that the $returnId variable is successfully echoed. next in the script is my second prepared statement, when it tries to run im getting the error that says:
Fatal error: Call to a member function bind_param() on a non-object in D:\filepath\register.php on line 17
it seems that my variable isnt being carried into the second prepared statement.
Your second query has syntax errors and failed to prepare. Since you have no error handling for database failures like this, your later code just blunders onwards:
$allergystmt = $db->prepare("INSERT INTO 'user_allergy' ('user_id', 'allergy_id') VALUES (?, ?)");
^--- ^--^--- ^-- etc...
You cannot use ' quotes on table and field names. ' indicate strings. None of those field/table names are reserved words, so there is NO need to quote them at at all:
$allergystmt = $db->prepare("INSERT INTO user_allergy (user_id, allergy_id) VALUES (?, ?)");
if (!$allergystmt) { die($dbh->errorInfo()); }
Note the addition of the errorInfo() output. Never assume a DB operation was successful. Always assume failure, and treat success as a pleasant surprise.

Using one ? to contain several variables in PHP MySQLi prepared INSERT statement

Trying to get to grips with prepared statements for an INSERT query. This is supposed to add a new user to the database:
$statement = $mysqli->prepare("INSERT INTO users (email,passwordhash) VALUES (?)");
$statement->bind_param('s', "'$email','$passwordhash'");
$statement->execute();
Is it correct to use a single ? and fill it with two values in that way?
The way mysqli doing that you need to bind all the variables separately
$statement = $mysqli->prepare("INSERT INTO users (email,passwordhash) VALUES (?,?)");
$statement->bind_param('ss', $email,$passwordhash);
$statement->execute();
But if you want it your way (say, you have an array ready and want to insert it using one placeholder) you need a helper class which will translate a custom placeholder into correct SQL statement:
$data = array('email'=>$email, 'passwordhash'=>$passwordhash);
$db->query("INSERT INTO users SET ?u");
and it will be shorter than raw mysqli yet will do much more - error handling, profiling and such.
Also keep in mind that when you will have a variable number of fields to insert, mysqli will turned to be a nightmare.
In prepared statements, each ? is used to replace one value.
When executed, your query will be:
INSERT INTO users (email,passwordhash) VALUES ("'email','password'")
That's not what you want. You need to use 2 ?s, one for each value.
$statement = $mysqli->prepare("INSERT INTO users (email,passwordhash) VALUES (?,?)");
// Pass each variable as a separate parameter
$statement->bind_param('ss', $email, $passwordhash);
$statement->execute();
Should be like this.
$statement = $mysqli->prepare("INSERT INTO users (email,passwordhash) VALUES (?,?)");
$statement->bind_param('ss', $email,$passwordhash);
$statement->execute();

PHP PDO Transactions?

I have a signup page and basically I need data inserted into 4 tables. I'm new to PDO and am confused over something.
Basically if any of the inserts fail I don't want anything added to the database, that seems simple enough.
My confusion is, I need to first insert the users username, email, password etc in my users table so I can get (not sure how) using PDO the uid MySQL has given my user (auto incremented by mysql). I need the user uid MySQL gave my user for the other tables as the other tables needs the uid so everything is linked properly together. My tables are InnoDB and I have foreign keys going from users_profiles(user_uid), users_status(user_uid), users_roles(user_uid) to the users.user_uid so they are all linked together.
But at the same time I want to ensure that if for example after data is inserted in the users table (so I can get the uid MySQL gave user) that if any of the other inserts fail that it removes the data that was inserted into the users table.
I thinks it's best I show my code; I have commented out the code and have explained in the code which may make it easier to understand.
// Begin our transaction, we need to insert data into 4 tables:
// users, users_status, users_roles, users_profiles
// connect to database
$dbh = sql_con();
// begin transaction
$dbh->beginTransaction();
try {
// this query inserts data into the `users` table
$stmt = $dbh->prepare('
INSERT INTO `users`
(users_status, user_login, user_pass, user_email, user_registered)
VALUES
(?, ?, ?, ?, NOW())');
$stmt->bindParam(1, $userstatus, PDO::PARAM_STR);
$stmt->bindParam(2, $username, PDO::PARAM_STR);
$stmt->bindParam(3, $HashedPassword, PDO::PARAM_STR);
$stmt->bindParam(4, $email, PDO::PARAM_STR);
$stmt->execute();
// get user_uid from insert for use in other tables below
$lastInsertID = $dbh->lastInsertId();
// this query inserts data into the `users_status` table
$stmt = $dbh->prepare('
INSERT INTO `users_status`
(user_uid, user_activation_key)
VALUES
(?, ?)');
$stmt->bindParam(1, $lastInsertID, PDO::PARAM_STR);
$stmt->bindParam(2, $activationkey, PDO::PARAM_STR);
$stmt->execute();
// this query inserts data into the `users_roles` table
$stmt = $dbh->prepare('
INSERT INTO `users_roles`
(user_uid, user_role)
VALUES
(?, ?)');
$stmt->bindParam(1, $lastInsertID, PDO::PARAM_STR);
$stmt->bindParam(2, SUBSCRIBER_ROLE, PDO::PARAM_STR);
$stmt->execute();
// this query inserts data into the `users_profiles` table
$stmt = $dbh->prepare('
INSERT INTO `users_profiles`
(user_uid)
VALUES
(?)');
$stmt->bindParam(1, $lastInsertID, PDO::PARAM_STR);
$stmt->execute();
// commit transaction
$dbh->commit();
} // any errors from the above database queries will be catched
catch (PDOException $e) {
// roll back transaction
$dbh->rollback();
// log any errors to file
ExceptionErrorHandler($e);
require_once($footer_inc);
exit;
}
I'm new to PDO and there maybe errors or problems above I have yet to notice because I can't test yet until I figure out my problem.
I need to know how I can insert the users data in the users table first so i can get the uid MySQL gave my user
Then get the uid as I need it for the other tables
But at the same time if a query fails for whatever reason after inserting into users table that the data is also deleted from the users table aswell.
This function returns primary key of just inserted record: PDO::lastInsertId
You will need it for NEED_USERS_UID_FOR_HERE parameter. Use it just after INSERT statement.
Since you started a transaction, data will not be inserted into any table if any error occures provided you use InnoDB engine for your MySQL tables (MyISAM doesn't support transactions).

Categories