How do I ensure all exceptions in this DB transaction are caught? - php

I use Yii and recently started using Transactions with try / catch blocks.
Here's how the code looks right now:
$dbConnection = Yii::app()->db();
try {
$transaction = $dbConnection->beginTransaction();
$dbConnection->createCommand("SELECT * from table_1")
->queryAll();
$transaction->commit();
} catch (Exception $ex) {
$transaction->rollback();
}
Suppose there's an exception with the DB (it's come up while unit-testing), I'm unable to rollback because the PHP dies with a fatal $transaction undefined error.
I'd rather not include isset() checks everywhere..
Is there a simpler way to make this work?

You can check if the exception is an instance of CDbException
$dbConnection = Yii::app()->db();
try {
$transaction = $dbConnection->beginTransaction();
$dbConnection->createCommand("SELECT * from table_1")
->queryAll();
$transaction->commit();
} catch (Exception $ex) {
if ($ex instanceof CDbException)
{
// handle CDBException
// ...
}
$transaction->rollback();
}

Related

How to catch SQL Exception in Laravel 5

Hay I'm creating a code like this :
\Log::info("saving log....");
try{
$data = AstronautCandidateLog::insert($request->logs);
}catch (SQLException $e)
{
\Log::info("SQL Exception happened");
}catch (Exception $e)
{
\Log::info("Exception happened");
}
\Log::info("status save data : ". $data);
But it seems that my Exception never get hit. So how to capture exception in laravel when something wrong in sql query...??
Thanks in advance.
Try this
try {
//Your code
} catch(\Illuminate\Database\QueryException $ex){
dd($ex->getMessage());
}
OR
use Illuminate\Database\QueryException;
try {
//Your code
} catch(QueryException $ex){
dd($ex->getMessage());
}
My case: add \ before Exception class otherwise it not handle
try
{
//write your codes here
}
catch(\Exception $e) {
Log::error($e->getMessage());
}
Make sure to use the Exception library in your controller. From there you can do:
try{
Some queries . . .
}catch(Exception $e){
return response()->json([
'error' => 'Cannot excecute query',
],422);
}
To implement an exception in Laravel, simply include the Exception class, at the top of your controller as
Use Exception;
Then wrap the lines of code you wish to catch an exception on using try-catch statements
try
{
//write your codes here
}
catch(Exception $e)
{
dd($e->getMessage());
}
You can also return your errors in json format, incase you are making an Ajax request, this time, remember to include the Response class at the top of your controller
Use Response;
Use Exception;
then wrap your codes in a try-catch statement as follows
try
{
//write your codes here
}
catch(Exception $e)
{
return response()->json(array('message' =>$e->getMessage()));
}
I hope this will solve your problem, you can learn more on Laravel and Error handeling here

php pdo catching connection exception with custom exception handler

What im trying to achieve here , is when the pdo connection throws an exception , my custom exception handler takes the message and passes it on so i can catch it with my custom exception handler.
try {
$mysqli = new PDO('mysql:host='.THOST.';dbname='.TDB.'', TUSER, TPASS);
}
catch (PDOException $e) {
$a = $e->getMessage();
throw new customException ( "Failed to connect to MySQL:". $a );
die();
}
catch (customException $e){
echo $e->errorMessage();
}
BUT it returns this error :
Fatal error: Uncaught exception 'customException' with message ......
Wrap it in another try-catch block.
try {
try {
$mysqli = new PDO('mysql:host='.THOST.';dbname='.TDB.'', TUSER, TPASS);
} catch(PDOException $e) {
$a = $e->getMessage();
throw new customException ( "Failed to connect to MySQL:". $a );
}
} catch(customException $e) {
echo $e->errorMessage();
// Do what you want
}
You are confusing custom exception handler with custom exception class. You need the former one and the other answer is wrong.
Explanation.
In your application code you have to writing one single line only:
$pdo = new PDO('mysql:host='.THOST.';dbname='.TDB.'', TUSER, TPASS);
without multiple tries and stuff. Just the code you need to run.
While all the handling logic goes into handler

Laravel: Using try...catch with DB::transaction()

We all use DB::transaction() for multiple insert queries. In doing so, should a try...catch be placed inside it or wrapping it? Is it even necessary to include a try...catch when a transaction will automatically fail if something goes wrong?
Sample try...catch wrapping a transaction:
// try...catch
try {
// Transaction
$exception = DB::transaction(function() {
// Do your SQL here
});
if(is_null($exception)) {
return true;
} else {
throw new Exception;
}
}
catch(Exception $e) {
return false;
}
The opposite, a DB::transaction() wrapping a try...catch:
// Transaction
$exception = DB::transaction(function() {
// try...catch
try {
// Do your SQL here
}
catch(Exception $e) {
return $e;
}
});
return is_null($exception) ? true : false;
Or simply a transaction w/o a try...catch
// Transaction only
$exception = DB::transaction(function() {
// Do your SQL here
});
return is_null($exception) ? true : false;
In the case you need to manually 'exit' a transaction through code (be it through an exception or simply checking an error state) you shouldn't use DB::transaction() but instead wrap your code in DB::beginTransaction and DB::commit/DB::rollback():
DB::beginTransaction();
try {
DB::insert(...);
DB::insert(...);
DB::insert(...);
DB::commit();
// all good
} catch (\Exception $e) {
DB::rollback();
// something went wrong
}
See the transaction docs.
If you use PHP7, use Throwable in catch for catching user exceptions and fatal errors.
For example:
DB::beginTransaction();
try {
DB::insert(...);
DB::commit();
} catch (\Throwable $e) {
DB::rollback();
throw $e;
}
If your code must be compartable with PHP5, use Exception and Throwable:
DB::beginTransaction();
try {
DB::insert(...);
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw $e;
} catch (\Throwable $e) {
DB::rollback();
throw $e;
}
You could wrapping the transaction over try..catch or even reverse them,
here my example code I used to in laravel 5,, if you look deep inside DB:transaction() in Illuminate\Database\Connection that the same like you write manual transaction.
Laravel Transaction
public function transaction(Closure $callback)
{
$this->beginTransaction();
try {
$result = $callback($this);
$this->commit();
}
catch (Exception $e) {
$this->rollBack();
throw $e;
} catch (Throwable $e) {
$this->rollBack();
throw $e;
}
return $result;
}
so you could write your code like this, and handle your exception like throw message back into your form via flash or redirect to another page. REMEMBER return inside closure is returned in transaction() so if you return redirect()->back() it won't redirect immediately, because the it returned at variable which handle the transaction.
Wrap Transaction
try {
$result = DB::transaction(function () use ($request, $message) {
// execute query 1
// execute query 2
// ..
});
// redirect the page
return redirect(route('account.article'));
} catch (\Exception $e) {
return redirect()->back()->withErrors(['error' => $e->getMessage()]);
}
then the alternative is throw boolean variable and handle redirect outside transaction function or if your need to retrieve why transaction failed you can get it from $e->getMessage() inside catch(Exception $e){...}
I've decided to give an answer to this question because I think it can be solved using a simpler syntax than the convoluted try-catch block. The Laravel documentation is pretty brief on this subject.
Instead of using try-catch, you can just use the DB::transaction(){...} wrapper like this:
// MyController.php
public function store(Request $request) {
return DB::transaction(function() use ($request) {
$user = User::create([
'username' => $request->post('username')
]);
// Add some sort of "log" record for the sake of transaction:
$log = Log::create([
'message' => 'User Foobar created'
]);
// Lets add some custom validation that will prohibit the transaction:
if($user->id > 1) {
throw AnyException('Please rollback this transaction');
}
return response()->json(['message' => 'User saved!']);
});
};
You should see that in this setup the User and the Log record cannot exist without eachother.
Some notes on the implementation above:
Make sure to return anything the transaction, so that you can use the response() you return within its callback as the response of the controller.
Make sure to throw an exception if you want the transaction to be rollbacked (or have a nested function that throws the exception for you automatically, like any SQL exception from within Eloquent).
The id, updated_at, created_at and any other fields are AVAILABLE AFTER CREATION for the $user object (for the duration of this transaction at least). The transaction will run through any of the creation logic you have. HOWEVER, the whole record is discarded when SomeCustomException is thrown. An auto-increment column for id does get incremented though on failed transactions.
Tested on Laravel 5.8
I'm using Laravel 8 and you should wrap the transaction in a try-catch as follows:
try {
DB::transaction(function () {
// Perform your queries here using the models or DB facade
});
}
catch (\Throwable $e) {
// Do something with your exception
}
in laravel 8, you can use DB::transaction in try-catch.
for example :
try{
DB::transaction(function() {
// do anything
});
}
catch(){
// do anything
}
if each of query be failed on try, the catch block be run.
First: using PostgreSQL database in Laravel makes things more tricky.
If you don't rollback after a transaction error, each futher queries will throw this error In failed sql transaction: ERROR: current transaction is aborted, commands ignored until end of transaction block. So if you can't save original error message in a table BEFORE the rollback.
try {
DB::beginTransaction(); //start transaction
$user1 = User::find(1);
$user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception $exception) {
$user2 = User::find(2); // ko, "In failed sql transaction" error
$user2->update(['field' => 'value']);
}
try {
DB::beginTransaction(); //start transaction
$user1 = User::find(1);
$user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception $exception) {
DB::rollBack();
$user2 = User::find(2); // ok, go on
$user2->update(['field' => 'value']);
}
Second: pay attention to Eloquent model attributes system.
Eloquent model keeps changed attributes after an update error, so if we want to update that model inside the catch block, we need to discard bad attributes. This isn't a dbtransaction affair, so the rollback command is useless.
try {
DB::beginTransaction(); //start transaction
$user1 = User::find(1);
$user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception|Error $exception) {
DB::rollBack();
$user1->update(['success' => 'false']); // ko, bad update again
}
try {
DB::beginTransaction(); //start transaction
$user1 = User::find(1);
$user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception|Error $exception) {
DB::rollBack();
$user1->discardChanges(); // remove attribute changes from model
$user1->update(['success' => 'false']); // ok, go on
}

Yii transaction not rollbacked

i have the following piece of code and i pass it some data to generate an exception and test if the transaction rollback is happening. It seems it's not and i'm not sure why.
can someone help me? thanks
$transaction = Yii::app()->db->beginTransaction();
try {
//.....
//call private methods
$category = MyController::saveCategory($params);
$test_saved = MyController::saveTest($params);
MyController::saveCommunity($param); // here is an exception and it should rollback the transaction but it doesn't
$transaction->commit();
} catch(Exception $e) {
$transaction->rollback();
throw new Exception($e);
}
private function saveCommunity($param){
$community = new Community();
$community->user_id = $user_id;
$community->name = $name;
$community->id = 71; // this is a duplicate primary key and will generate an exception
try{
$community->save(false, null);
}catch(Exception $e){
throw $e;
}
return $community;
}
(mysql tables are set to innodb)
Try changing your code responsible for exception throwing:
try{
$community->save(false, null);
}catch(Exception $e){
throw $e;
}
to something like:
if(!$community->save(false, null))
throw new Exception('Error saving');
And remove the exception throwing here:
} catch(Exception $e) {
$transaction->rollback();
//throw new Exception($e);
}
By default pdo doesn't throw exceptions and just ignores errors.
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

What's **if (no errors)**?

I am trying to implement transactions to Kohana but it seems to be not so easy as in Spring/Java.
So far I found this code to try but I don't know how to replace the part (no errors)
DB::query('START TRANSACTION');
// sql queries with query builder..
if (no errors)
DB::query('COMMIT');
else
DB::query('ROLLBACK');
How do I make the if clause?
Normally transactions are handled as such:
DB::query('START TRANSACTION');
try {
//do something
DB::query('COMMIT');
} catch (Exception $e) {
DB::query('ROLLBACK');
}
What this means is if everything succeeds within the try block, great. If any part of it fails then it won't reach the commit and will jump to the catch block, which contains the rollback. You can add more error handling within the catch if you wish, even throw a new exception of your own or throw the same exception you caught.
Just wrap everything in a try/catch block:
DB::query('START TRANSACTION');
try {
// sql queries with query builder..
DB::query('COMMIT');
} catch (Database_Exception $e) {
DB::query('ROLLBACK');
}
DB errors are converted to exceptions:
DB::query('START TRANSACTION');
try {
// sql queries with query builder..
DB::query('COMMIT');
}
catch($e)
{
$this->template->body = new View('db_error');
$this->template->body->error = 'An error occurred ...';
DB::query('ROLLBACK');
}
If you're using Kohana 3:
$db = Database::instance();
$db->begin();
try
{
// Do your queries here
$db->commit();
}
catch (Database_Exception $e)
{
$db->rollback();
}

Categories