DB Transaction not rolling back [Laravel 7.3] - laravel

I'm running Laravel 7.3 on an app, and I feel like this is potentially a Laravel framework bug because I'm racking my brain on this...
I've got a DB Transaction with model saves within it, all within a try/catch but it's still saving the model updates, and I just can't see why because it hits the catch() and the response json hits, so I assume the rollback is running, yet the database is still updating.
try {
DB::beginTransaction();
foreach ($models as $model) {
$model->rungroup_id = $rungroup->id;
$model->zone_id = $rungroup->getFirstZoneID();
$model->delivery_status = 'next';
$model->save();
}
DB::commit();
return response()->json(['status' => 'success']);
} catch (Throwable | Exception $e) {
DB::rollBack();
Log::error($e);
return response()->json([
'status' => 'error',
'message' => $e->getMessage(),
], 500);
}
Any ideas?

After updating Laravel and many other attempts at rewriting the code to see if I made a silly mistake, I ended up finding that the person who created the database tables set the Table engine to MyISAM rather than InnoDB which is required for the DB transactions... I wish Laravel threw an error so I would've known, but this has definitely saved my sanity, and hope it helps others.

Related

Why does Laravel Cashier silently catch Stripe exceptions?

There are numerous places in Laravel Cashier where exceptions are trapped within the Cashier code, but not really handled in any meaningful way - not even logged. This results in bizarre behaviour if there is something wrong with the Stripe request (e.g. invalid price_id specified), where a null value is returned with no indication about why.
Anyone know why they have silently 'trapped' these errors making it impossible for the calling code to report?
e.g. Laravel\Cashier\Concerns\ManageInvoices::upcomingInvoice():
public function upcomingInvoice(array $options = [])
{
if (! $this->hasStripeId()) {
return;
}
$parameters = array_merge([
'automatic_tax' => $this->automaticTaxPayload(),
'customer' => $this->stripe_id,
], $options);
try {
$stripeInvoice = $this->stripe()->invoices->upcoming($parameters);
return new Invoice($this, $stripeInvoice, $parameters);
} catch (StripeInvalidRequestException $exception) {
//
}
}

Lumen : 1s, Laravel : 5s for a simple api call?

So I started a project not long ago for an API on Laravel, and I thought why not give Lumen a shot. But in the end, I want to use Sanctum, Socialite, etc... And I read pretty much everywhere that the performance difference is not that big nowadays anyway.
So I migrated my code from Lumen to Laravel, and after a few tweaks, everything works as before... Except that now a very simple API call takes 5s. Granted, it might be my setup - wsl2 isn't particularly fast. But still, the same call in Lumen was taking ~1000ms.
Route::post('register', [AuthController::class, 'register']);
Controller:
public function register(Request $request): JsonResponse {
$this->validate($request, [
'phone' => 'required|string|phone',
'phone_country' => 'required_with:phone',
]);
$phone = phone($request->get('phone'), [$request->get('phone_country')]);
try {
$user = User::createByPhone($phone);
return response()->json(['user' => $user->id, 'message' => 'SMS_SENT'], 201);
} catch (\Exception $e) {
return response()->json(['message' => 'User Registration Failed - ', 'error' => $e], 409);
}
}
Function in model:
public static function createByPhone($phone) {
return DB::transaction(function () use ($phone) {
$user = User::create();
$user->phoneNumbers()->create([
'did' => $phone
]);
return $user;
});
}
So, pretty simple stuff. Now, why is that taking so long? ~6000ms. Am I missing something?
(On a more general note, is there a way to cut from Laravel things that aren't needed for an API only?)
Thanks ahead.
I don't see anything really wrong with your code. I guess this has something to do with the speed wsl2 can read files. We had issues with windows machines and Laravel in Docker. We added Swoole to our project and this helped alot on WSL2.
Laravel now has a first party package called Octane to add Swoole to your project. You can try and install that to see if it helps.

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) {
}
});

L4.2 Event: know where event got fired from?

I have a question regarding Events with Laravel 4.2...
I currently have an event listener on "auth.login"... some code lines are executed when user logins on web version... however I would like to execute a different action if the user logged via the API controller, example: ApiController#postLogin (my mobile version).
Code in my home controller:
if (Auth::attempt(['email' => Input::get('login'), 'password' => Input::get('password')]) OR Auth::attempt(['username' => Input::get('login'), 'password' => Input::get('password')]))
{
return Redirect::intended(URL::route('dashboard.index'));
}
else
{
return Redirect::action('HomeController#getIndex')->with('poplogin', true)->with('badcredentials',true)->withInput();
}
Code in global.php (event listener)
Event::listen('auth.login', function($user)
{
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
Session::flash('justlogged',true);
//other code that I didnt include..........
});
Code in my ApiController
public function getRefreshData() {
//check the token
$token = Input::get('token');
$username = Input::get('username');
$user = User::where('api_token', $token)
->where('username', $username)
->first();
if(!$user || !$token) {
return Response::json([
'error' => true,
'message' => 'Invalid Token, please re login',
'code' => 401],
401
);
}
Auth::login($user);
//5 last Timesheets + tslines, for pre-load at log-in in phone memory
//Not inserting possible creation dates between, to keep phone app 100% independent
$timesheets = $user->timesheets()->orderBy('startdate', 'DESC')->take(10)->with('tslines')->get();
//Other code that I didnt include
);
return $response;
}
I cannot control the execution of the event "auth.login" myself.. firing it manually with parameter would just double-fire the event (i think?)
Is there a way to detect where the event got fired from in the Event:listen and do not insert a "log-in attemp" (my code in event listener) each time I use the getRefreshData() function in my API? Yes, I need to log the user in my API function (for other code that isn't included)
Edit: It seems to me that the most straightforward way to handle this is to check for the token in the Event listener.
Event::listen('auth.login', function($user)
{
if (Input::has('token') && Input::has('username')) {
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
Session::flash('justlogged',true);
//other code that I didnt include..........
}
});
I really would suggest, long term, looking at using the functionality demonstrated in the docs under Accessing the Logged In User, it's just going to make life easier.
Original response: It might be helpful if you posted more code, because I feel like maybe this is an instance where if we zoom out a little bit maybe there is a better way to deal with this situation. Possibly you need multiple actions, different listeners, etc.
For solving this issue though, it's easy, just pass in whatever additional data you need to via a parameter:
$response = Event::fire('auth.login', array($user, 'source' => 'ApiController#postLogin', 'mobile' => true));
Then you can set those parameters to the $event object that is passed to your listener.
Let me know if you have any further questions!
After some research, I found how I could 'bypass' the execution of the event listener when the event is fired from the ApiController, using the Request::is() function
From L4.2 Docs: http://laravel.com/docs/4.2/requests#request-information )..
My routes.php file is like so:
Route::controller('api/v1', 'ApiV1Controller');
And in my global.php (where I declare my event listener)
Event::listen('auth.login', function($user)
{
if (!Request::is('api/*'))
{
//Code that is always executed at firing of event, except when from my API controllers
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
}
}

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

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

Resources