Calling SQL rollback on php exception - php

Consider the following scenario:
I open a new connection to MySQL, using mysqli extension
I start a transaction
I do some queries on the MySQL database and to some php computation
Either MySQL or php may throw some exceptions
I commit the transaction
(pseudo) code:
$sql = new mysqli();
...
{ //some scope
$sql->query("START TRANSACTION");
...
if (something)
throw Exception("Something went wrong");
...
$sql->query("COMMIT"); // or $sql->commit()
} //leave scope either normally or through exception
...
$sql->query( ... ); // other stuff not in transaction
If all the above is in some big try catch block (or perhaps even the exception is uncaught) the transaction will remain unfinished. If I then try to do some more work on the database, those actions will belong to the transaction.
My question is: is there some possible mechanism that would permit me to automatically send $sql->rollback() when I leave the transaction-creating scope in an abnormal way?
Some notes:
mysqli::autocommit is not the same as a transaction. It is just a setting for autocommit you turn on or off. Equivalent to MySQL SET autocommit.
mysqli::begin_transaction is not documented and I don't know what is its precise behavior. Will it call rollback when the mysqli object dies for example?

The Command mysqli::begin_transaction is the same (object oriented way) as your $sql->query("START TRANSACTION");;
There is no way, to auto rollback on exception.
You can only comit, if everything has success. Then it will be a "auto rollback" if not. But this way, you will have trouble very soon, if you have more then one commit.
So your current code is allready very good. I would do it the full OOP way:
$sql->begin_transaction();
try {
$sql->query('DO SOMETHING');
if(!true) {
throw new \Exception("Something went wrong");
}
$sql->commit();
}
catch (\Exception exception) {
$sql->rollback();
}
You also can write your own Exception:
class SqlAutoRollbackException extends \Exception {
function __construct($msg, Sql $sql) {
$sql->rollback();
parent::__construct($msg);
}
}
But you still need to catch it.

You cannot automatically rollback on exception, but with a little code you can do what you want. Even if you already are in a try-catch block you can nest them for your transaction section, such as this:
try {
$sql->query("START TRANSACTION");
...
if (something)
throw new PossiblyCustomException("Something went wrong");
...
$sql->query("COMMIT");
} catch (Exception $e) { //catch all exceptions
$sql->query("ROLLBACK");
throw $e; //rethrow the very same exception object
}
Even if you use the most generic catch Exception, the actual type of the exception is known. When you rethrow, it can still be caught by PossiblyCustomException later. Thus, all the handling you already have remains unaffected by this new try-catch block.

So you want to either commit the transaction or rollback the transaction if there was a problem, and there are many ways that a problem could occur - either through a mysql error or php throwing an exception. So why not set up a flag at the start of the transaction, set that flag when the transaction ready to be committed, and check the state of the flag when the process is done to see if a rollback is needed?
If you are afraid of globally scoped variables you could use an overly complicated Singleton class or some kind of static variable for this, but here is the basic idea:
function main() {
global $commit_flag = false;
start_transaction();
doEverything();
if ($commit_flag) {
commit_transaction();
} else {
rollback_transaction();
}
}
function doEverything() {
global $commit_flag;
try {
/* do some stuff that may cause errors */
$commit_flag = true;
} catch { ... }
}

You can handle mysql exceptions using DECLARE ... HANDLER Syntax, but I don't find it proper to try to handle them through PHP !
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
INSERT INTO Users ( user_id ) VALUES('1');
INSERT INTO Users ( user_id ) VALUES('2');
COMMIT;
END;
More information can be found here :
http://dev.mysql.com/doc/refman/5.5/en/declare-handler.html

Related

Atomic transactions and logging

Let's say I have a function that modifies a database. This could be anywhere from one query to multiple queries in a transaction. In any of these modifications we want to make sure all queries are successful. Thankfully for transactions, if one of these fails I have a way of making sure none of the changes are made permanent.
Now let's say I also have a log that needs to be written to to make note of the change (s). How could I make sure that both the modifications are made and the log written?
I could always do a try/catch on either but say for instance my queries are successful but my log is unsuccessful. How could I then go back and undo all of the modifications to the database?
Would something like this be effective and/or advisable?
try {
$db->connect();
$db->mysqli->begin_transaction();
// series of queries
...
// log to file
$db->mysqli->commit();
} catch (exception $e) {
$db->mysqli->rollback();
$db->mysqli->close();
}
Wanted to make note of some behaviors I observed while testing this. If you place the commit() before the log, the database changes will not roll back even if an exception is caught while logging.
The data will be stored in the database once you call commit() or trigger an implicit commit. For example, this will not make any changes to the database because there is no commit:
$mysqli->begin_transaction();
$mysqli->query('INSERT INTO test1(val) VALUES(2)');
// end of script's execution
In your little example, the transaction will be rolled back as long as your logging functionality stops the execution or throws an exception.
$db->connect();
try {
$db->mysqli->begin_transaction();
// series of queries
...
// log to file
throw new \Exception(); // <-- This will prevent commit from executing.
$db->mysqli->commit();
} catch (\Exception $e) {
// An exception? That's ok, we'll try something different instead.
$db->mysqli->rollback();
}
You need to call rollback only if you want to recover from the exception and clear the unsaved buffer. The only time you should ever catch exceptions is if you want to somehow recover from it and do a different thing. Otherwise, your code could be simplified by removing try-catch and rollback altogether.
However, it is a good idea to catch exceptions in transactions and roll it back explicitly as soon as possible, even when you do not want to recover. It can prevent an issue if you catch the exception somewhere else in your code and you try to execute other DB actions. The unsaved data is still in the buffer until you close the session or roll back! This is why you would often see code like this:
$db->connect();
try {
$db->mysqli->begin_transaction();
throw new \Exception(); // <-- Something throws an exception
$db->mysqli->commit();
} catch (\Exception $e) {
// rollback unsaved data, but rethrow the exception as we do not know how to recover from it here
$db->mysqli->rollback();
throw $e;
}

What is the difference between DB::beginTransaction() and DB::transaction()?

I'm using Laravel 5.2.
I would like to know what are the differences between :
DB::beginTransaction() and DB::transaction()
DB::commitTransction() and DB::commit()
DB::rollbackTransction() and DB::rollback()
Any helps would be appreciated.
DB::beginTransaction() will only begin a transaction, while for DB::transaction() you must pass a Closure function that will be executed inside a transaction.
So this:
DB::transaction(function() {
// Do something and save to the db...
});
is the same as this:
// Open a try/catch block
try {
// Begin a transaction
DB::beginTransaction();
// Do something and save to the db...
// Commit the transaction
DB::commit();
} catch (\Exception $e) {
// An error occured; cancel the transaction...
DB::rollback();
// and throw the error again.
throw $e;
}
As you can see, DB::transaction() is a "helper" function to avoid writing code to catch errors, begin a transaction, commit the transaction, and optionally rollback (cancel the transaction) if an error occured.
If you have a more complex logic, or need an specific behaviour, you will manually build your transaction; if your logic is rather simple, DB::transaction() is the way to go.
As for DB::commitTransaction() and DB::rollbackTransaction(), I can't find information.
It's a good practice to check the source code of the things you use, because you will learn how they are written, as well as how to write. Here's the file with the source for these methods.
From Laravel 6 to use Transactions must be use the following helper functions.
use Illuminate\Support\Facades\DB;
try {
// For Begin a transaction
DB::beginTransaction();
// Do something
// Commit the transaction
DB:: commit();
} catch (\Throwable $e) {
// An error occured
DB::rollback();
// and throw the error again.
throw $e;
}
if you use the DB::commitTransaction(); function, get error undefined function.

Pdo Error Catching Try/Catch

After looking into using try catch blocks for my Pdo statements is it really a benefit? Does this just slow your code down?
I believe that there should be a try catch around the connection command in case the database connection fails. But does there really need to be try catch around each pre prepared statement? These should never change and never really error out.
Any thoughts?
I'm using Php and MySql.
There is no benefit to this:
try {
// exec statement
// exec statement
}
catch (Exception $e) {
// do nothing
}
If you aren't going to do anything with the error and provide a reasonable solution, then you may as well let the exception bubble up to the application's main "something went wrong" error page.
But you may want to do this:
// begin transaction
try {
// exec statement
// exec statement
// commit transaction
}
catch (Exception $e) {
// rollback transaction
// handle error or rethrow $e;
}
And prepared statements can throw exceptions. Perhaps a unique key is violated, or a foreign key constraint is, etc.
But the main point is, you don't use exceptions to hide or silence errors. You use them to catch an error, process it intelligently, and continue on accordingly.

try catch vs if else in PDO and some other things

There is this question that I found:
What is the advantage of using try {} catch {} versus if {} else {}
If you can add anything to it then please do as I am new to PDO, also what does this mean;
$dbc->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
On the MySQL website it says "ensures the creation of exceptions rather than errors" but I do not understand this, can anyone please elaborate on it please.
Exceptions are catchable via try/catch and are classes with properties, while proceedural errors cannot and are not. Proceedural errors are instead handled by PHP's native error handling.
You can observe the behavior difference by manually triggering them.
to throw an Exception:
throw new Exception();
to trigger a proceedural error:
trigger_error($message, U_USER_ERROR);
Exceptions are bassically the OO way of error handling. Find more information about Exceptions here: http://php.net/manual/en/language.exceptions.php
Well, they are pretty similar. In PHP native error handling you can trow your own errors with trigger_error($error, [$level]) as you can throw your own exceptions with throw new MyException($error); you can set a default error handler with set_error_handler() which will manage all PHP error (except parsing) your own way as you can set a default exception handler with set_exception_handler(). Both PHP native errors and exceptions are automatically triggered/thrown somehow: PHP native errors compiling the script, exceptions if you are using specific items such as (PDO) or something.
Now let's try the same code with different approaches:
With PHP native errors you can do things such as:
$db = new Database();
if ($db === NULL) { trigger_error("Cannot connect to the database", E_USER_ERROR); }
$result = $db->query("UPDATE charlieiscool SET iloveunicorns = 1 WHERE userid = 1");
if (!$result) { trigger_error("Error updating table", E_USER_ERROR); }
$file = 'log.php';
if (!file_exists($file) or !file_get_contents($file)) { trigger_error("$file not found", E_USER_ERROR); }
require($file);
I think this does not really need any explanation. If an error is triggered, the entire script is skipped and you see the error. There are no more things you can do; you could set E_USER_ERROR or E_USER_NOTICE or E_USER_WARNING and handle them differently, but you have not that big choice. Now take a look at a possible OOP approach with try{} catch() {} blocks:
try {
$db = new Database();
if (!$db) { throw new DBEx("Cannot connect to the database"); }
$result = $db->query("UPDATE charlieiscool SET iloveunicorns = 1 WHERE userid = 1");
if (!$result) { throw new QueryEx("Query failed"); }
} catch (PDOException $e) {
echo $e->getMessage();
} catch (DBEx $e) {
$e->customFunction();
} catch (QueryEx) {
$e->customFunctionForQuery();
}
try {
$file = 'log.php';
if (!file_exists($file) or !file_get_contents($file)) { throw new FileEx("$file does not exists"); }
require($file);
} catch (FileEx) {
$e->fileGenerate();
$e->logError();
}
The main difference is that if the first try{} block throw an exception the second try{} will be executed any way. In fact if an exception is thrown, only the rest of the script inside that try{} block will be skipped.
Another difference (the one i love most) is that you can create several classes (extending mains Exception or PDOException or others) and customize very much your error handling behavior. You have unlimited possibilities to customize your classes, adding functions, editing already existing ones. You can add specific function (such as $e->fileGenerate();) and call them inside the catch() {} block where needed.
Notice also that if you want your entire script to stop if an error occurred, means that that error needs trigger_error(); instead if you want that an error only stops a specific block of code related to that error then it's time for try and catch.
You should not use one replacing the other, you should use one beside the other evaluating each errors as it is.
By the way PDO::setAttribute() change default values and options in your database handler. You can for example change your default fetch (used in PDOStatement::fetch()) with $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);.
References:
PDO::setAttribute()
set_error_handler()
set_exception_handler()
Exceptions
Predefined exceptions

What causes a rollback failed error in mySQL?

I get the following error when I try to perform a rollback.
Rollback failed. There is no active transaction.
I searched for this issue and found a few suggestions that recommend disabling the autocommit setting. But I am unsure how to do this. Is there any other reason for the above error? I am using MYSQL and Zend and my php.ini file loaded the required drivers.
MySQL works in autocommit by default. You can turn it off with:
$connection->setAttribute(Doctrine_Core::ATTR_AUTOCOMMIT, false);
Another idea I have is you didn't start the transaction (which should disable autocommit in Doctrine):
$connection->beginTransaction();
The class UnitOfWork.php has a catch block like:
catch (Exception $e) {
$this->em->close();
$conn->rollback();
throw $e;
}
Of course, if your class is not ready to find an already closed entity manager and therefore connection, you will have this exception. The worst thing about it is that it masks the underlying cause of the exception, since the error was caused before the catch block being executed. To fix it, you can do a simple check in your class' catch block:
catch(Exception $e) {
if($conn->isTransactionActive()) {
[rollback]
[close]
[rethrow] (if necessary)
}
}
Found the problem.....I have called rollback() function 2 times in different places in the code
You can check if the transaction exists with transaction nesting level:
$this->em->getConnection()->getTransactionNestingLevel()
If nesting level exist grather than 0, then you can do a rollback

Categories