I would like to use transaction in some critical areas of my code, but really not for everything I do.
I just learnt that there is an AUTOCOMMIT value that is set to 1 by default, and I should set it to 0 if I want to START TRANSACTION and COMMIT or ROLLBACK.
Is there a better way to handle that autocommit?
How do I know if it is set or not ?
Does it's value change to AUTOCOMMIT=1 on every page that loads?
Using PDO
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
try {
// do stuff
$pdo->commit();
} catch (Exception $ex) {
$pdo->rollBack();
throw $ex;
}
Related
I need to redirect to a separate page to show error. header("Location: errorpage.php?errorcode=11"); after does not seem to work.
<?php
$db = new PDO("mysql:host=localhost;dbname=mydbase;charset=utf8", "user", "password");
try {
$db->beginTransaction();
$db->exec("SOME QUERY");
$db->commit();
}
catch(PDOException $ex) {
$db->rollBack();
//Something went wrong so I need to redirect
header("Location: errorpage.php?errorcode=11");
}
PDO's error handling is a little unusual. It has modes for throwing real exceptions, issuing PHP warnings, or just being silent.
Silent is the default. What has happened here is that no exception is ever thrown because you did not configure PDO to throw one. So the catch block is never entered and header() is never called. Setup your $db object to throw exceptions:
// Ensure PHP's native error handling is showing
// on screen (to catch problems with header() itself)
error_reporting(E_ALL);
// Always in development, disabled in production
ini_set('display_errors', 1);
$db = new PDO("mysql:host=localhost;dbname=mydbase;charset=utf8", "user", "password");
// Turn on exceptions for PDO so the try/catch is meaningful
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
$db->beginTransaction();
$db->exec("SOME QUERY");
$db->commit();
}
catch(PDOException $ex) {
$db->rollBack();
//Something went wrong so I need to redirect
header("Location: errorpage.php?errorcode=11");
// Always make an explicit call to exit() after a redirection header.
exit();
}
About PDO::exec():
I know this is example code, but exec() is not usually the right method to use. If you are doing DDL statements, the transaction won't work since MySQL doesn't support that, and if you are doing things like INSERT/UPDATE/DELETE with any user input, you should be doing prepare()/execute() to create an injection-safe prepared statement instead.
What is the syntax preferred while using PDO transaction and try catch and Why?
$dbh->beginTransaction();
try {
} catch (Exception $e) {
}
OR
try {
$dbh->beginTransaction();
} catch (Exception $e) {
}
The existent answers seem to suggest that since $dbh->beginTransaction() could throw a PDOException it should be in the same try block of the actual transaction code, but this means that the rollBack() code itself will be wrong, because it could invoke a rollBack() without there being a transaction, which could also throw another PDOException.
The right logical ordering of this is that you put the code you want executed in one transaction in one catch block after the transaction has been created. You could also check that the return of beginTransaction() is true before proceeding. You could even check that the database session is in a transaction before calling rollback().
if ($dbh->beginTransaction())
{
try
{
//your db code
$dbh->commit();
}
catch (Exception $ex)
{
if ($dbh->inTransaction())
{
$dbh->rollBack();
}
}
}
Keep in mind that you could still, at least in theory, get an exception from beginTransaction() and rollBack() so I would put this in a separate function and enclose the invocation in another try-catch block.
You could also bubble the exception you get up to catch it and log all Exceptions in one place. But remember that some exceptions could be data integrity errors such as duplicate keys or invalid foreign keys, which would not be a database fault as such, but most probably a bug in your code.
With this approach, the main thing to keep in mind here is that the two try-catch blocks have a slightly different purpose. The inner one is purely to ensure that multiple queries are executed and committed atomically in one transaction and if something happens they are rolled back. The external try-catch would be to detect erroneous situations and log it, or whatever you would want to do if you have a problem with your database.
try {
$dbh->beginTransaction();
} catch (Exception $e) {
}
Simply because an exception could be thrown as you attempt to begin the transaction.
Note that you can place another try catch block inside the initial try.
The second one usually makes most sense. Since you may not always know what will cause the transaction to fail, you would want the rollback (and possibly commit) logic available in the catch, so you'd want to put the beginTransaction() inside of the try.
In addition to the try/catch make sure you set the error mode attribute:
$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
And if anything you should catch the PDOExecption just because you should not treat all exception in the same way.
try {
$dbh->beginTransaction();
//do stuff
$dbh->commit();
} catch (PDOException $e) {
$dbh->rollBack();
//...
throw $e;
}
But if you still want to catch other exception add more catch blocks:
try {
....
} catch (PDOException $e) {
//handle pdo exception
}catch (Exception $ex) {
//handle others differently
}
Will this work? I'd test it but I don't know how to crash things half way though.
$db = DB::getDB();
try{
$db->begintransaction();
Invoice::saveInvoice($info, $db);
InvoiceDetails::saveDetails($moreInfo, $db);
$db->commit();
}catch(Exception $e){
$db->rollback();
}
And if it does work is there anything that could bite me in the butt besides doing something that causes a implicit commit?
The only thing I'd do is fix up your exception handling. For example
catch (Exception $e) {
$db->rollback();
throw $e;
}
Doing this lets you safely rollback the transaction as well as letting the error bubble up further in your application.
You could even wrap the inner exception (which will probably be a PDOException) with one of your choosing, eg
$db->rollback();
throw new RuntimeException('Error saving invoice details', 0, $e);
To "crash things half way though", simply throw an exception within one of your save* methods, eg
throw new Exception('KA-BLAM!');
Should I do
$dbh->beginTransaction();
try{
Or
try{
$dbh->beginTransaction();
It doesn't really matter, it will run the code indifferent of it's position.
But you want to put the rollback() in the catch, and with that setup it's not readable if you put begin outside.
I would vote for inside the try.
It probably doesn't really matter. However, it's better to place the beginTransaction outside the try. When the beginTransaction fails, it should not execute the rollback.
add it inside the try/catch block, so you can catch any PDOException:
try {
$dbh->beginTransaction(); // start transaction
$stmt = $dbh->query($query); // run your query
$dbh->commit(); // commit
} catch(PDOException $ex) { // if exception, catch it
$dbh->rollBack(); // rollback query
echo $ex->getMessage(); // echo exception message
}
In this case it doesn't matter, as beginTransaction will return false on failure. If it threw exceptions, you would want it inside of a nested try block (otherwise you'd execute rollBack() after catching the exception which would fail because no transaction was started).
If you want to catch the possible errors that the beginTranscation method should throw, go for the second one.
I have following code:
try{
$db->beginTransaction();
$handler = $db->prepare(...);
$handler->execute()
$query2 = "INSERT INTO...";
$db->exec($query2);
$db->commit();
}catch (PDOException $e) {
try { $db->rollBack(); } catch (Exception $e2) {}
}
My question is, does the rollBack() rollbacks all changes caused by both, execute() and exec()? The reason for using exec() is that I have to dynamically create the $query2 and this way it is much easier for me.
Any operations performed between the start of a transaction and the point you do the rollback are reversed. Doesn't matter HOW you did those operations - they'll be rolled back.
Of course, this assumes you're using transaction-capable databases/tables. For instance, if your exec() was done on a MyISAM table in MySQL, and the execute() on an InnODB table, then InnoDB operation will get rolled back, but you're stuck on the MyISAM one.