Return Date To Client From Within DB::Transaction Closure() - laravel

I am executing several database saves within my db transaction closure:
DB::transaction(function() {
...
});
However what i want to do now is when a transaction fails instead of throwing an exception i want to return a custom JSON object straight t the client, if the transactions succeeds i want to do the same thing.
This is my object:
return [
'code' => '',
'message' => '',
'data' => []
];
How would i return the above to the client from within the closure?

Instead of DB::transaction Closure you can use DB::beginTransaction, DB::commit and DB::rollback methods in order to have more control in code, you can wrap your DB actions like this :
DB::beginTransaction();
try {
DB::insert(...);
DB::insert(...);
DB::insert(...);
//If everything is ok we commit
DB::commit();
return response()->json(["status" => "success"])
} catch (Exception $e) {
//something goes wrong, we rollback
DB::rollback();
return response()->json(["error" => "Some error"]);
}
You can research more in database transaction docs

Related

Transaction in Laravel rolled back incompletely

I have an array of Organization model. Each Organization contains an array of User model.
There is a many-to-many relationship between them using the OrganizationUser model.
If I have problems inserting an Organization, an User, or a relationship between them, I want to roll everything back.
But I also use "try catch" blocks to see at which stage the error occurred.
To test this, I explicitly made a mistake in the OrganizationUser relationship object. I'm using an User id that can't be in the Organization.
This rolls back the Organization, relationships, and first User, but all other Users are added to the database.
DB::beginTransaction();
$org->save();
foreach ($org->user_list as $user) {
try {
$user->save();
} catch (\Exception $exception) {
DB::rollback();
Log::error('Error user insert : '. $user->inn .' '. $exception->getMessage());
}
try {
$orgs_user = OrganizationUser::create([
'user_id' => 80,
'org_id' => $org->id,
]);
} catch (\Exception $exception) {
DB::rollback();
Log::error('Insert user_org relationship : ' . $exception->getMessage());
}
}
When I didn't use the "try catch" block it worked well
When you call DB::rollBack() (by the way it's rollBack with a capital B), that ends the transaction. The rest of the queries will execute as normal.
The safest way to use transactions is to put the entire thing in a single try catch. The \Exception itself should contain enough information to know what caused it in its message or stack trace.
try {
DB::beginTransaction();
$org->save();
foreach ($org->user_list as $user) {
$user->save();
$orgs_user = OrganizationUser::create([
'user_id' => 80,
'org_id' => $org->id,
]);
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
}
It is a bit verbose, but you can also use the DB::transaction() method.
DB::transaction(function () use ($org) {
$org->save();
foreach ($org->user_list as $user) {
$user->save();
$orgs_user = OrganizationUser::create([
'user_id' => 80,
'org_id' => $org->id,
]);
}
});
DB::rollback does not stop the execution of the code.
If you are in a catch block the transaction will be rolled back but the next block (as well as the rest of the iteration) will continue.
Use a break; after the rollback to break out of the loop completely like below:
DB::beginTransaction();
$org->save();
foreach ($org->user_list as $user) {
try {
$user->save();
} catch (\Exception $exception) {
DB::rollback();
Log::error('Error user insert : '. $user->inn .' '. $exception->getMessage());
break;
}
try {
$orgs_user = OrganizationUser::create([
'user_id' => 80,
'org_id' => $org->id,
]);
} catch (\Exception $exception) {
DB::rollback();
Log::error('Insert user_org relationship : ' . $exception->getMessage());
break;
}
}
You can also return; or just exit() depending on how you want to handle the error

How to catch custom error raised in method of object?

On laravel 9 site in my custom class method raise custom error with error message
<?php
class CustomClass
{
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Exceptions\HttpResponseException;
...
public function method(): array
{
if(false) {
throw new HttpResponseException(response()->json([
'message' => $validated['message'],
], 422));
}
but I failed to catch this error in control where methods raised :
{
try {
$imageUploadSuccess = $customClass->method(
...
); // This method can raise different types of exception
}
catch (ModelNotFoundException $e) { // exception with invalid id is catched - THIS PART WORKS OK
return response()->json([
'message' => 'Item not found',
], 404);
}
catch (HttpResponseException $e) { // I catch HttpResponseException with is generated in method
// None of these 2 methods returns custom error message in method :
\Log::info($e->getMessage()); // empty value - I NEED TO GET THIS MESSAGE TEXT
\Log::info($e->getCode()); // 0 value
\Log::info($e->getTraceAsString());
return response()->json([
'message' => $e->getMessage(),
], 422);
}
return response()->json(['success' => true], 400);
Have I to use other class, not HttpResponseException ? I which way ?
Thanks in advance!
You’re not throwing your custom exception so it’s never raised so it won’t be catchable.
Replace where you throw the HttpResponseException with your Exception.
<?php
class CustomClass
{
public function method()
{
$isValid = false;
if(!$isValid) {
throw new SomeCustomException(response()->json([
'message' => $validated['message'],
], 422));
}
}
}
You would then use do something like:
$customClass = new CustomClass();
$customClass->method();
Note how I have defined a variable $isValid and how I check it for a true or false value in the if statement. This is because if checks for truthy values and false will never be `true so your custom exception would never be thrown.
if (false) {
// code in here will never execute
}

Laravel transaction saves data into DB even when it's rollbacked

I send a request, trying to save multiple documents at once.
Here is my code
DB::beginTransaction();
try {
foreach ($request->documents as $index => $documentInfo) {
// Check
if (// some statement) {
$documentExists = Document::where([
// some checks
])
->exists();
if ($documentExists) {
throw new \Exception("Error Processing Request", 1);
}
}
// Assign document properties
$document->save();
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
if ($request->ajax()) {
return response()->json([
'success' => false,
'document' => $index,
'message' => '// message',
]);
}
return redirect()->back()->with('error', '// message');
}
The thing that happens is that I check every document for its own unique values. If two documents with same values try to get saved I want to return an error.
I tried uploading two documents with the same values, the error is returned on the second document and the transaction must fail, but the first document gets saved into the database.
I don't want this, if there is an error I don't want any documents saved into the DB.

display message for database insert operation

Is there any mechanism that we can know that the data sent from app is successfully inserted to database, as in stored procedure, we use output parameter.
How can we be sure that the data is inserted into database and in case if the data insertion operation is failed, how to catch the error cause and display in a user friendly way?
// controller code
public function store(Request $request)
{
$validatedInput = $request -> validate([
'NBookName' => 'required|string|min:3|max:100',
'NBookId' => 'required|string|min:2|max:10|unique:books,BookID', // unique:table_name,column_name
'NBookUnitPrice' => 'required|max:5|'
]);
$book = new Book;
$book -> BookName = $request->input('NBookName');
$book -> BookID = $request->input('NBookId');
$book -> BookUnitPrice = $request->input('NBookUnitPrice');
$book->save();
return view('pages.about');
}
The Eloquent method save returns either true or false, thus checking the return of the save will let you know if the operation was successful.
You can alternatively use the method saveOrFail which will also return true / false, but may also give a Throwable
From the documentation: https://laravel.com/api/5.3/Illuminate/Database/Eloquent/Model.html#method_save
Examples:
if( $book->save() ){
return response()->json($book, 200);
} else {
return response()->json(['error' => 'Could not save'], 400);
}
or
try {
$book->saveOrFail();
return response()->json($book, 200);
} catch( \Exception $e ){
return response()->json(['error' => $e->getMessage()], 400);
}
save() will return a boolean, saved or not saved. So you can either do Like:
$saved = $book->save();
if(!$saved)
{
App::abort(500, 'Error');//Or return any message to view
}
For more details:check-if-laravel-model-got-saved-or-query-got-executed
I'd start off by separating the layer, I don't like watching validations, Eloquent queries and view related output in the same place.
Before reading the answer below, take a look at
https://laravel.com/docs/5.6/validation#form-request-validation
I'd make middleware to authenticate and authorize users, then use formrequests to validate the input data, and by the time it reaches the controller, the user is authenticated, authorized, data validated and all it's needed is call someone that is responsible for the insert operation and return something to the UI.
"How can we be sure that the data is inserted into database and in
case if the data insertion operation is failed"
Generally, a try catch will do:
public function store(Request $request)
{
try {
$validatedInput = $request -> validate([
'NBookName' => 'required|string|min:3|max:100',
'NBookId' => 'required|string|min:2|max:10|unique:books,BookID', // unique:table_name,column_name
'NBookUnitPrice' => 'required|max:5|'
]);
$book = new Book;
$book -> BookName = $request->input('NBookName');
$book -> BookID = $request->input('NBookId');
$book -> BookUnitPrice = $request->input('NBookUnitPrice');
$book->save();
} catch (\Exception $e) { 
// return your pretty response
// If the operation saving to the database fails, you'll get the information here on whatever it is about,
// as the generic exception catchs it. It should catch the stored procedure output aswell
}
return view('pages.about');
}
If you plan on having a series of insertions and updates/deletes and you don't want them to break logic (insert some and stop at inserting when 1 is wrong), you can use DB; and use DB::beginTransaction(), DB::commit() and DB::rollback() on your code
https://laravel.com/docs/5.6/database

Disable Query_Log() before executing some queries in Laravel

I am developing web base application using Laravel 5.6 . There are many database queries to execute.
As a security purpose I try to store my all queries into a database table as Query log. I have uses AppServiceProvider service provider to do that. Now I want to disable Query_Log() function for a while that prevent storing particular database query also.
when I run app with above code, It was running while exceeding database maximum execution time.
Can somebody suggest me how I do that?
public function boot()
{
if(env('App_Debug')){
DB::listen(function($query){
//DB::connection()->disableQueryLog();
Query_Log::insert([
'query_string'=>$query->sql,
'user' => "Admin",
'created_at' =>Carbon::now()->toDateTimeString(),
]);
});
}
}
This is how i exclude the listener. i don't know if there is existing function
DB::listen(function ($query) {
try {
//check if the query log is the excluded table
if (preg_match('(query_activities)', $query->sql) == 0) {
QueryActivity::query()->create([
'connection_name' => $query->connectionName,
'time_taken' => $query->time,
'query' => $query->query,
'bindings' => Utils::aesEncrypt(json_encode($query->bindings)),
]);
}
} catch (\Exception $exception) {
}
});

Resources