Transactions using PDO - php

My understanding is the InnoDB is now the default engine for MySQL. With that knowledge, I am beginning to delve into transactions.
Here is what I have so far...
try{
$pdo->beginTransaction();
$stmnt = $pdo->prepare ("delete from playing where uniq = :uniq");
$stmnt->bindParam (':uniq',$uniq);
$stmnt->execute();
$stmnt = $pdo->prepare ("insert into removals (playdate, time, vid) values (:playdate, :time, :vid");
$stmnt->bindParam (":playdate",$playdate);
$stmnt->bindParam (":time", $time);
$stmnt->bindParam (":vid", $vid);
$stmnt->execute();
$pdo->commit();
echo "1"; // success
return;
}
catch (PDOException $e){
$pdo->rollback();
echo $e->getMessage();
}
This is called by jQuery with a result of "1" indicating a success.
If I understand this correctly, if bot statements execute successfully, they will both be "committed" however it either fails, no database activity will take place and an error message will be generated detailing the first statement execution that fails.
My real question is whether the begin transaction and commit should reside within or outside the try...catch block.
Thanks,
-dmd-

For readability and cleanliness, yes it should be inside the try block. But it really does not matter. It just declares what to commit or rollback if you call roll back.

Related

recovering from transaction failure in php

SQL queries can fail for a number of reasons, even though the same query ran 100 times before without a problem. I'd like to detect weather a transaction failed. I found 2 ways of doing this:
1: use a ton of if else statements
$mysqli->begin_transaction();
$stmt = $mysqli->prepare("INSERT INTO testtable VALUES (?,?,?)");
if ( false===$stmt ) {
$mysqli->rollback();
issueInternalError();
}
$rc = $stmt->bind_param('iii', $x, $y, $z);
if ( false===$rc ) {
$mysqli->rollback();
issueInternalError();
}
$rc = $stmt->execute();
if ( false===$rc ) {
$mysqli->rollback();
issueInternalError();
}else{
$mysqli->commit();
}
$stmt->close();
2: perform query in try catch block
try{
$mysqli->begin_transaction();
$stmt = $mysqli->prepare("INSERT INTO testtable VALUES (?,?,?)");
$stmt->bind_param('iii', $x, $y, $z);
$stmt->execute();
$mysqli->commit();
}catch(Exception $e){
$mysqli->rollback();
issueInternalError();
}
Using try/catch halves the code and it makes it really readable, but will the second code correctly catch all possible errors? or better will issueInternalError() always be executed if an error exists?
UPDATE:
I've added this code to the beginning of php file
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
I am getting mixed results in my tests:
This kind of error is catched successfully:
$mysqli->begin_transaction();
$stmt = $mysqli->prepare("INSERT INTO testtable VALUES (?,?,?)");
$stmt->bind_param('iiissss', $x, $y, $z); //error here is successfully catched
but this isn't
$mysqli->begin_transaction();
$stmt = $mysqli->prepare("INSERT INTO testtable VALUES (?,?,?)");
$stmt->bind_param('iii', $x, $y, $z,""); //error not catched
It looks like the second one is not caused by mysqli, therefore it isn't thrown.
Your try-catch will not catch all errors. It will only catch exceptions. There's a difference between errors and exceptions in PHP.
The following try-catch will catch only exceptions:
try {
// ...
} catch(Exception $e) {
// ...
}
To catch both exceptions and errors you can use Throwable interface. As a side note, it is best to always provide fully specified name for classes and interfaces in PHP.
try {
// ...
} catch(\Throwable $e) {
// ...
}
Mysqli can throw both exceptions and errors. Exceptions are usually triggered when mysqli can't process a command or the execution of the command failed on the MySQL server-side. These exceptions are instances of mysqli_sql_exception exception class.
If there is a programmer error on the PHP side, e.g. an incorrect number of arguments or invalid arguments, then mysqli will trigger a PHP error. These can be various errors, but usually, they all derive from the base class Error.
Warning: Prior to PHP 8 many errors were simply warnings. These have been properly categorized now. Additionally, a number of software bugs were fixed in mysqli that didn't trigger an exception when one should occur. For best result, please use the latest PHP release.

MySQL transaction: queries committed anyway

I discovered PDO and transactions few days ago and they're great. Today I wrote my first transaction but it didn't end as expected. To test it I put a wrong inexistent table in one of the two queries and, independently of which one, the other (correct) one is committed anyway.
I read the syntax structure on some websites and it seems to be correct, but maybe the error is right there. The tables are InnoDB. Or better, phpMyAdmin, into the db overview's table, reports MyISAM on the last summary row but the column "type" of each table's row reports InnoDB; I think this could be 'cause MyISAM is the default type on the server, is that right?
Here's the code:
$conn = new PDO($db->getDsn(), $db->getUsername(), $db->getPassword());
try {
$conn->beginTransaction();
$stmt = $conn->prepare('INSERT INTO trips (country_code, year, img, showX) VALUES (:country_code, :year, :img, :showX)');
$stmt->execute(array(':country_code' => strtolower($country_code), ':year' => $year, ':img' => $rename_response, ':showX' => $show_hide));
$tripID = $conn->lastInsertId();
$stmt = $conn->prepare('INSERT INTO trips_multilang (tripID, lang, country_name) VALUES (:tripID, :lang, :country_name)');
$stmt->execute(array(':tripID' => $tripID, ':lang' => $trip_lang, ':country_name' => strtolower($country_name)));
$conn->commit();
} catch (PDOException $e) {
$conn->rollBack();
die($e->getMessage());
}
Add to your $db class another method, $db->getOptions(), that returns an array
return [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
and then call PDO as
$conn = new PDO($db->getDsn(), $db->getUsername(), $db->getPassword(), $db->getoptions());
After that your code will start to catch up.
For now you are catching exceptions for nought, as none actually thrown.
On a side note, you must catch Exception, not PDOException and also you must re-throw an exception in case of error instead of dying out.

Transactions in php function or in mysql stored procedure?

What is better way to begin a transaction?
Inside procedures or PHP functions?
For example I calling MySQL procedure like this:
function sendLeaguesToDb(){
$leagues = "";
try{
$this->PDO->beginTransaction();
$stmt = $this->PDO->prepare("call insupd_Leagues(:id,:name,:country,:sport_id,:his_data,:fixtures,:livescore,
:numofmatches,:latestmatch)");
$leagues=$this->soccer->GetAllLeagues();
foreach($leagues as $key=>$value){
$stmt->bindParam(':id',$value->Id);
$stmt->bindParam(':name',$value->Name);
$stmt->bindParam(':country',$value->Country);
$stmt->bindParam(':sport_id',$value->Sport_Id);
$stmt->bindParam(':his_data',$value->Historical_Data);
$stmt->bindParam(':fixtures',$value->Fixtures);
$stmt->bindParam(':livescore',$value->Livescore);
$stmt->bindParam(':numofmatches',$value->NumberOfMatches);
$stmt->bindParam(':latestmatch',$value->LatestMatch);
$stmt->execute();
$this->PDO->commit();
}
}
catch(XMLSoccerException $e){
echo "XMLSoccerException: ".$e->getMessage();
}
catch(PDOException $e){
echo "PDOException: ".$e->getMessage();
$this->PDO->rollback();
}
}
Is this good way if I want to send/get data fastest possible every minute/hour?
It depends on what you're trying to achieve.
If you want to see all the inserts as an 'atomic operation' you are doing right, as if one call to the SP fails, the rollback will undo all the changes made from the previous calls
If, otherwise, you want to "isolate" every single SP call, assuring that if it succedes the results are stored in the DB, you have to start and end the transaction inside the SP
I think the preferred solution is the first
EDIT: one thing i'm noting now: the commit should be after the for :
try{
$this->PDO->beginTransaction();
$stmt = $this->PDO->prepare("call insupd_Leagues(:id,:name,:country,:sport_id,:his_data,:fixtures,:livescore,
:numofmatches,:latestmatch)");
$leagues=$this->soccer->GetAllLeagues();
foreach($leagues as $key=>$value){
$stmt->bindParam(':id',$value->Id);
$stmt->bindParam(':name',$value->Name);
$stmt->bindParam(':country',$value->Country);
$stmt->bindParam(':sport_id',$value->Sport_Id);
$stmt->bindParam(':his_data',$value->Historical_Data);
$stmt->bindParam(':fixtures',$value->Fixtures);
$stmt->bindParam(':livescore',$value->Livescore);
$stmt->bindParam(':numofmatches',$value->NumberOfMatches);
$stmt->bindParam(':latestmatch',$value->LatestMatch);
$stmt->execute();
}
//move your commit here
$this->PDO->commit();
}

My PDO transaction doesn't commit, but also doesn't throw any exception

I have some queries that have run perfectly up until now, but I wanted to wrap them in a transaction to decrease the likelihood of data corruption. After running the code, everything APPEARS to work (i.e. no exception is thrown), but the query is never committed to the database. I've looked at other questions on S.O. but I haven't found any that apply to my case.
Here's my code:
db::$pdo->beginTransaction(); // accesses PDO object method
try {
$sql = "INSERT INTO expenses SET date=:date, amount=:amount, accountId=:account; ";
$sql .= "UPDATE accounts SET balance = balance - :amount WHERE id = :account";
$s = db::$pdo->prepare($sql);
$s->bindValue(':date', $date);
$s->bindValue(':amount', $amount);
$s->bindValue(':account', $account);
$s->execute();
db::$pdo->commit();
echo 'success';
}
catch (PDOException $e) {
db::$pdo->rollback();
echo '<p>Failed: ' . $e->getMessage() . '</p>';
}
When I run the code, it outputs the success message, but like I said, nothing gets committed to the database.
Since it's relevant, I should also note that my PDO error mode is set to ERRMODE_EXCEPTION, so I don't think that's what's causing the problem. I'm running a MySQL database (InnoDB)
Any thoughts?
I am not completely sure how running multiple queries in a single query statement works (if it works) because I typically do transactions by running each query separately in a prepare/execute statement. The transaction will still queue up all the changes until commit/rollback as expected. According to this SO answer it appears to work, however the example is not binding values and so that might be another issue.
I would suggest just splitting up the queries into multiple prepare/bind/execute statements and it should work as expected.
The success message will allways be displayed because it's just there to echo.. if you want to show succes only after the execute you should use if statement.. for the not committing part it could be many things.. are you sure all values $date, $amount, $account are being supplied ? you can also try starting with the simple insert and if that works do the update..
$sql = 'INSERT INTO expenses (date, amount, account)
VALUES(:date, :amount, :account)';
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':date', $);
$stmt->bindValue(':amount', $amount);
$stmt->bindValue(':account', $account);
if($stmt->execute()) {
db::$pdo->commit();
echo 'success';
}
not sure about the db::$pdo->commit(); part because I use some kind of OOP way, but hope this might help.
I think I had similar problem in one of the projects, please add another catch block just after the one you have so the code will look like:
catch (PDOException $e) {
db::$pdo->rollback();
echo '<p>Failed: ' . $e->getMessage() . '</p>';
}
catch (Exception $exc) {
db::$pdo->rollback();
echo '<p>Failed: ' . $exc->getMessage() . '</p>';
}
It will allow you to catch Exceptions of class other than PDOException, so you could ensure more.
UPDATE:
Please consider splitting two queries into 2 PDO statements llike this:
$sql = "INSERT INTO expenses SET date=:date, amount=:amount, accountId=:account; ";
$s = db::$pdo->prepare($sql);
$s->bindValue(':date', $date);
$s->bindValue(':amount', $amount);
$s->bindValue(':account', $account);
$s->execute();
$sql2 = "UPDATE accounts SET balance = balance - :amount WHERE id = :account;";
$s2 = db::$pdo->prepare($sql2);
$s2->bindValue(':amount', $amount);
$s2->bindValue(':account', $account);
$s2->execute();
I use this approach in my entire project with success so I hope it might help.
Don't use db::$pdo->commit();
Use
$sql = "INSERT INTO expenses SET date=:date, amount=:amount, accountId=:account;";
$sql .= "UPDATE accounts SET balance = balance - :amount WHERE id = :account;COMMIT;"; // change here
kind of silly ;(

how to find record insert to mysql using commit()

Sorry for this beginners question and i'm not a PHP developer, but now i'm trying to learn it.
i want to add record in MySQL data base and i'm using transactions lock.
my code is as below.
$SqlQuery="INSERT INTO tab_photo VALUES('$PhotoID','$ProjectId','$Day','$barCode','$photoName','$PhotoXml')";
$waiting = true;
while($waiting) {
try {
// save border data
$stmt = $conn->prepare($SqlQuery);
$conn->beginTransaction();
$stmt->execute();
sleep(1);
$x=$conn->commit();
echo "x value-".$x;
echo "Success";
$waiting = false;
}
catch (PDOException $e){
echo "Failled :".$PhotoID."-".$PhotoID;
if(stripos($e->getMessage(), 'DATABASE IS LOCKED') !== false) {
// This should be specific to SQLite, sleep for 0.25 seconds
// and try again. We do have to commit the open transaction first though
$conn->commit();
usleep(250000);
} else {
$conn->rollBack();
throw $e;
}
}
}
in here as output it gives,
x value-1 Success
but actually this record doesn't add to the database.
My Questions:
Even the commit is successful(output 1) how does it not added to the database?
how can i check whether record is added to database? ( Is there any way to find it without write select statement?
As I understand, you expect that PDOException will be thrown when statement is failed to execute. But as I can see, exception is not thrown by default in such cases.
See how you can change that here
Suppose in your case you should have a code like this:
$conn = new PDO($connection_string);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // this will force PDO to throw exception when SQL statement fails instead of simply setting an error.
Suppose this will work fine for you.
Please note that you should not use
$SqlQuery="INSERT INTO tab_photo VALUES('$PhotoID','$ProjectId','$Day','$barCode','$photoName','$PhotoXml')";
Instead of that, you should use parameters binding:
$SqlQuery="INSERT INTO tab_photo VALUES(:PhotoID,:ProjectId,:Day,:barCode,:photoName,:PhotoXml)";
$stmt = $conn->prepare($SqlQuery);
$conn->beginTransaction();
$stmt->execute(array(':PhotoID' => $PhotoID, ':ProjectId' => $ProjectId, ....));
sleep(1);
See this for more details.

Categories