PHP PDO Transactions? - php

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).

Related

PHP MySQLi prepared statement not inserting data into table

I am designing a website with user login and registration with email verification, and when the user registers an account, it will successfully, and correctly, insert the user record into the database in the "users" table.
But when I try to pull the users id that is created when inserted to insert them into a second table for a roster of all members of the site (this is for a gaming clan), it inserts the user id as 0 in the roster table, regardless of what their id is on the "users" table for that user.
heres my code:
if($insert_stmt = $db->prepare("INSERT INTO users (username, password, email, date, actcode) VALUES (?, ?, ?, ?, ?)"))
{
$insert_stmt->bind_param('sssss', $username, $password, $email, $date, $actcode);
$stmt = $db->prepare("SELECT id FROM users WHERE email = ?");
if($stmt)
{
$stmt->bind_param('s', $email);
$stmt->execute();
$stmt->fetch();
$stmt->store_result();
$stmt->bind_result($ui);
}
$stmt = $db->prepare("INSERT INTO roster (userid, joindate) VALUES (?, ?)");
if($stmt)
{
$stmt->bind_param('ss', $ui, $date);
$stmt->execute();
}
if(!$insert_stmt->execute()) {
header('Location: error.php?err=Registration failure: INSERT');
}
}
for security reasons I have left certain variables and sections of the code omitted that do not have any bearing on this section causing me a headache.
I can't figure out why it is not inserting the newly creating "id" from "users" for that user account into the "roster" table under the "userid" column.
Also, just to test something, I also went and set a session variable
$_SESSION['uid'] = $ui;
directly after the line
$stmt->store_result($ui);
and echoed it on my index.php file, and it shows the session variable as 0 as well.
You could simply use this function: mysqli::$insert_id instead of writing your second query.
Replace this line:
$stmt = $db->prepare("SELECT id FROM users WHERE email = ?");
With:
$userid = $db->insert_id;
Let me know if this works.
Still wasn't able to get it to work with the the solutions provided, but decided to just merge everything into the users table, which fixed the issue. As far as checking if it is executing, I am putting in some checks to make sure it goes through. Thanks for the help and advise.

conditional insert column values

i am having a problem with inserting records to my tables as i want to insert values into specific columns if only the value is not null, my query goes like this
i have tried:
INSERT INTO users(id,name,phone,address) VALUES($userId,$userName,$userPhone,$userAddress);
but it gives me error if on client side one of the parameters is not sent not all the time the client side send all the parameters (id,name,phone,address) i want to have some kind of condition instead of the handle all combinations to the query to go over this problem
You should be using prepared queries, but your immediate problem is that you aren't quoting anything:
$query = "INSERT INTO users(id,name,phone,address) VALUES('$userId', '$userName', '$userPhone', '$userAddress')";
Now, assuming you're doing this properly using a PDO connection, this is how you should be doing it to protect your database:
$db = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
$query = "INSERT INTO users (id, name, phone, address) VALUES (?, ?, ?, ?)";
$stmt = $db->prepare($query);
$result = $stmt->execute(array($userId,$userName,$userPhone,$userAddress));
if ($result) {
//success
} else {
//failure
}
Put ' inside VALUES () like '$userId'.
INSERT INTO users(id,name,phone,address) VALUES('$userId','$userName','$userPhone','$userAddress');
If parameter are not coming, then let it Insert Null in DB Table column. (Allow Null). And, if you don't want to insert NULL in DB Table column, then assign any default value before inserting.

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.

Proper usage of php mysqli autocommit and rollback

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");
}

Insert not working, but select finds the data on same page

I'm inserting a simple entry into a database-table using PDO (php/mysql). The connection is set up correctly and it seems that the data is inserted. Later on the same page (after the insertion) I call a select statement to get all data from that table. It returns everything, including the newly inserted column (and it's incremented id).
But when I refresh the site - or just have a look at the table in phpmyadmin - the new entry is gone... And there is no delete code yet.
Here is the code for insertion
// prep statement
$stmt = $pdo->prepare("INSERT INTO garage (created_at, created_by, modified_at, modified_by, caption, description, picture) VALUES (:created_at, :created_by, :modified_at, :modified_by, :caption, :description, :picture);");
// binding params, all rows return true
$stmt->bindParam(":created_at", $ts, PDO::PARAM_STR);
$stmt->bindParam(":created_by", $user_id, PDO::PARAM_INT);
$stmt->bindParam(":modified_at", $ts, PDO::PARAM_STR);
$stmt->bindParam(":modified_by", $user_id, PDO::PARAM_INT);
$stmt->bindParam(":caption", $caption, PDO::PARAM_STR);
$stmt->bindParam(":description", $description, PDO::PARAM_STR);
$stmt->bindParam(":picture", $filename, PDO::PARAM_STR);
$stmt->execute(); // returns true
A few lines of code later:
$selStmt = $pdo->prepare("SELECT id, caption, description, picture FROM garage;");
$selStmt->execute();
while ($row = $selStmt->fetchObject()) {
echo $row->id; // output is working, id is next increment
}
Any suggestions? Thanks in advance!

Categories