Parallel transactions on database lead to incorrect result, laravel - php

My application is implemented with php/laravel and MySQL database. In this system, users can deposit money to wallet. I have a wallet_balance column in the users table. When users deposit funds into their wallet, I update that column as follows:
public function topup(Request $request)
{
$user = auth()->user();
$deposit_amount = $request->deposit_amount;
//e.g.
//$deposit_amount = 100
//$user->wallet_balance = 50
$user->wallet_balance = $user->wallet_balance + $deposit_amount;
$user->save();
// After saving I expect $user->wallet_balance to be 150 which works perfectly.
}
There are some services that users are charged where money is deducted from their wallet (in another function). For example:
public function chargeService(Request $request)
{
$user = User::findOrFail($request->user_id);
$service_fee = $request->service_fee;
//$service_fee = 30
//$user->wallet_balance = 150
$user->wallet_balance = $user->wallet_balance - $service_fee;
$user->save();
// After saving I expect $user->wallet_balance to be 120 which works perfectly.
}
After the two transactions, the user's wallet balance should be 120. However, on very rare cases the two transactions might happen concurrently. That means in the deposit transaction, the initial wallet balance is 50 and also for the service fee transaction the initial wallet balance is 50 (because they queried the database at the same time before any of the two updated the wallet balance). Here is the danger. The first transaction will have the resulting wallet balance as 150 (50 + 100), and the second will have it as 20 (50 - 30). This leaves the wallet balance of the user as either 150 or 20, depending on which operation updates the user's wallet balance last.
Now, my question is, how can I approach my wallet system so as to avoid this stinking issue. I will appreciate so much guys.
You can suggest a better way to phrase the question.

The main issue you're facing is that the User state that is loaded in PHP doesn't reflect what's in your database. You should either execute a query against the database:
$params = ['fee' => 30, 'user_id' => $request->user_id];
DB::statement('UPDATE users SET wallet_balance=wallet_balance-:fee WHERE id=:user_id', $params);
or lock the record for updating:
try {
DB::beginTransaction();
// it is important to get the state from the
// database at the time of locking
$user = User::query()->lockForUpdate()->findOrFail($request->user_id);
$user->wallet_balance = $user->wallet_balance - $service_fee;
$user->save();
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
}

You have to lock the row of table using raw mysql query and i dont know if eloquent has some features for that type of query but you can dig in.
Have a look at this article to get a clear picture https://medium.com/#ibraheemabukaff/locking-rows-in-mysql-e84fd3bbb8cd

Related

Voting system that being run at the same time not all saved

so i am working at voting system that have code like this
public function storeVote(Request $request)
{
$voting = Voting::findOrFail($request->voting_id);
if($voting->status == 1){
$checkVote = vote::where('voting_id',$request->voting_id)->where('name',$request->name)->where('voting_candidate_id',null)->first();
if($checkVote){
\DB::beginTransaction();
try{
$candidate = candidate::findOrFail($request->voting_candidate_id);
$skor = $candidate->skor + 1;
$candidate->skor = $skor;
$candidate->update();
$checkVote->voting_candidate_id = $request->voting_candidate_id;
$checkVote->update();
$vote_ok = $voting->vote_ok + 1;
$voting->vote_ok = $vote_ok;
$voting->update();
event(new VotingEvent($skor, $voting->id, $candidate->id));
CandidateProfile::flushCache();
\DB::commit();
return response()
->json([
'saved' => true,
'message' => 'Voting done.',
]);
} catch (\Exception $e){
\DB::rollBack();
abort(500, $e->getMessage());
}
}else{
return response()
->json([
'saved' => false,
'message' => 'sorry, you already vote'
]);
}
}else{
return response()
->json([
'saved' => false,
'message' => 'Sorry, Voting session not started yet'
]);
}
}
so this function act as a way for user to vote, the participant have a unique link where they only need to choose the candidate and then it will be trigger the function above
the problem is when i tested to do like 30 vote at the same time, half of them not saved.
any idea why?
update:
the data that are not saved:
candidate skor is not updated or not multiplied
voting information about vote_ok which mean total vote that being use
Note there is a catch when you use update queries. For eg: in you above code you are updating the candicate_skor using;
$skor = $candidate->skor + 1;
$candidate->skor = $skor;
$candidate->update();
The problem arises when your server gets multiple concurrent requests for the same route. For each of the requests (let's say you have 5 requests) the function retrieves the old candidate_skore value let's say it was equal to 1. Now when each of them updates the value DB value it will be equal to 2. Even though you have 5 upvote requests that should update the DB value to 6 it updates to just 2, causing you to lose 4 votes.
Ideally, you should keep a relation table for all the votes received and only insert it into that relation table. That way even if simultaneous requests are served all of them will insert new entries to the table. Finally, your total vote should be equal to the count of all rows in that relation table.

Verification of payments and ceiling of trainings in Laravel

My goal is that when I encode a student for a payment. He (student) can follow 2 trainings per payment. (this is the ceiling - 1 payment = 2 trainings)
In my form Payment, I encode a student for example Menier.
The student is entitled to two trainings, (this is the ceiling)
In my form Training: I encode 2 trainings for the student Menier.
My first question: how can I block the number of trainings by two?
(so, if I encode another training, it must block!)
My second queston, if I encode 2 payments for the same student. The student is entitled to 4 trainings. How to create this type of algorithm?
Here is my code for now, I know it's not a lot...
Edit 05-10-2019 - Controller Training
public function store(Request $request)
{
$request->validate([
'date_seance' => 'required',
'hour_start' => 'required',
'hour_end' => 'required',
'fk_motorbike' => 'required',
'fk_former' => 'required',
'fk_student' => 'required',
'fk_typeseance' => 'required'
]);
$date_seance = $request->get('date_seance');
$hour_start = $request->get('hour_start');
$hour_end = $request->get('hour_end');
$fk_motorbike = $request->get('fk_motorbike');
$fk_student = $request->get('fk_student');
$fk_former = $request->get('fk_former');
$fk_typeseance = $request->get('fk_typeseance');
$payments = Payment::where('fk_student', $request->get('fk_student'))->first();
if(!isset($payments)){
return redirect()->route('trainings.index')
->with('error', 'No payment, no training! ');
}
$thisStudentsTrainings = Training::where('fk_student', $fk_student)->get();
if(count($thisStudentsTrainings) >= 2){
return redirect()->route('trainings.index')
->with('error', 'The ceiling is 2 trainings! ');
}
$thisStudentsPayments = Payment::where('fk_student', $request->get('fk_student'))->get();
if( (count($thisStudentsPayments) * 2) < count($thisStudentsTrainings) ) {
return redirect()->route('trainings.index')
->with('error', 'test!');
}
else{
Training::create($request->all());
return redirect()->route('trainings.index')
->with('success', 'Add');
}
}
Do you have an idea of how I could solve my problems, I am still a beginner in laravel.
For Watercayman
Thank you for your time.
This is not too bad to do. Since the payment is not directly associated with a specific training (ie you have a credit system), you can do this pretty easily with a couple of queries.
My first question: how can I block the number of trainings by two?
Start with the basics and find the number of trainings in the database for this student:
$thisStudentsTrainings = Training::where('fk_student', $fk_student)->get();
Or you can come in from the reverse for this simply query:
$student = Student::with('trainings')->get();
$thisStudentsTrainings = $student->trainings;
Then, to limit to two trainings (without payment consideration yet):
if(count($thisStudentsTrainings) >= 2){ too many trainings }
Now that you have a count of trainings, if you also want to make sure they have a payment in the system, lets get the payments:
$thisStudentsPayments = Payment::where('fk_student', $request->get('fk_student'))->get();
To check if they have paid for trainings, you now have both pieces of data that you need. You just have to figure out if they have paid for the right amount of trainings based on 2 payments = 1 training. So:
if( (count($thisStudentsPayments) * 2) < count($thisStudentsTrainings) ) {
// They have not made enough payments!
}
My second queston, if I encode 2 payments for the same student. The student is entitled to 4 trainings. How to create this type of algorithm?
The above will work for 2 or 4 or whatever you want.
Now, if you want to enforce a max of 2 trainings per each payment, we can check on this too. BUT, this is starting to get a little complex or circular in the logic. If you can avoid this, it will be a lot easier. But, let's check on the max of 2 per payment, which is just the adding an equals check, AFTER the one above:
if( (count($thisStudentsTrainings) >= count($thisStudentsPayments) * 2) {
// They are at their limit of trainings based on their payments!
// Note we include >= so that if they have purchased 2 trainings,
// this blocks them from a 3rd until they pay again.
}
This should solve your issue. However, you didn't ask, but I assume you don't want a student to allow a training if they have already used up a payment. IE if they've taken a training and they have 'spent their credit', they should not be allowed to take the training. If this is important to you, I suggest that in another part of your program, you write to the database when a payment has been consumed. So - if a student uses 2 trainings and has paid for them, maybe a boolean field on the Payment model spent (or something to indicate the payment is no longer valid). You could also remove the payment from the system if you don't need historical data. But, assuming you do, and you use $payment->spent, you can still do the above algorithm, just add the spent line to the query something like:
$student = Student::with(['trainings' => function($query){
$query->where('spent', 0)
}])->get();
Then all the rest should be the same. This isn't cut & paste, but I think now that you have separated out payments and trainings, this should be a pretty easy solve to understand based on the above. :)

Codeigniter Unilevel MLM earning Distribution

I am here to have some help from you.
I am making a Unilevel MLM using Codeigniter
and now I can sucessfully add new member
But the problem is I need to distribute the earnings to other level
after a new member is successfully Added
See pic below:
Distribution of earnings
I need to distribute like the image above.
I hope you can help me with this guys.
Okay, I have a solution for you. The process i used is based on my understanding of the question.
So this is it, first i checked for a registration post, if a post request is made, i use the referral id from the post to fetch the number of registrations tied to that referral id that has not been given awarded the 100 earning. If the count of the result of this query is equal to 4, i loop through all of them and give them the earning of 100 and update their paid status to reflect that they have been paid then i insert the record, else i just insert the record.
So too much text, lets see the code
//this is the controller code
//first check for post of registration
if($_POST){
//kindly do your form validation here
$register = array(
"name" => $this->input->post('name'),
"refid" => $this->input->post('refID')
);
//during post, get the referral id from the post
$refID = $this->input->post('refID');
//before registering, use referral id to get the referred that have not been given earnings yet
$thereffered = $this->referral_m->getReferred($refID);
//check for the number of registration
if(count($thereffered) == 4){
//then get their ids and give them their earnings and update them to paid
foreach($thereffered as $referred){
$earnings = array(
"userID" => $referred->id,
"amount" => 100
);
$paid = array(
"paid" => 1
);
//give earning
$this->referral_m->giveEarning($earnings); //this adds the user id to earning table and give it an amount of 100
$this->referral_m->updateUser($paid, $referred->id); //this updates the user with the paid status
}
//then proceed to register the new record
$this->referral_m->register($register);
}else{
//register the new record
$this->referral_m->register($register);
}
//redirect after registration
redirect();
}else{
//load view here
}
This is how the model looks like
function getReferred($refID){
return $this->db->get_where('referral', array("refid" => $refID, "paid" => '0'))->result();
}
function giveEarning($record){
$this->db->insert('earnings', $record);
}
function register($array){
$this->db->insert('referral', $array);
}
function updateUser($array, $id){
$this->db->where('id', $id);
$this->db->update('referral', $array);
}
From the model, you would discover that i created 2 database tables, I assume you already have those tables created, just use the logic to update your code. If you find any difficulty, kindly comment lets sort it out

Laravel 4.2 - storing in database is very slow for large amount of data

I'm building an application that handles several thousand apartments per month.
This means several inserts, updates and selects in different tables per apartment per month.
I've used in-built laravel/eloquent syntax, nothing in raw sql, as Im not familiar with it very well.
I have a button that generates the info for a month for all apartments. I wasnt very pleased of having to put all that in one action but it was required of me.
Now, every time I click the button and generate the info it gets harder and harder to process as the database fills each month. I assume its normal considering the situation. Could I get an advice on how to optimize this? I have to wait like 30 seconds - 1 minute for it to finish.... which is not ok...
This is how my controller basically runs (this is the main storing function, which calls several other functions):
// define month
$month = '01-04-2017';
// identify all payment categories (like water, electricity, maintenance etc)
// all these must be treated separately as they are calculated in very different ways
$payment_categories = PaymentCategories::get();
$payments_array = [];
foreach($payment_categories as $payment_category){
$total = Transactions::where('month', $month)->where('payment_category_id', $payment_category->id)->sum('amount');
$payments_array[$payment_category->id] = $total;
}
$apartments = Apartments::get();
foreach($apartaments as $apartment){
$this->storeMainData($apartment);
// each of these categories must be treates separately as they are calculated in very different ways
foreach($payment_categories as $payment_category){
$total_amount = $payments_array[$payment_category->id];
if($payment_category->id == '1'){
$this->storePaymentCategory1($apartment, $payment_category, $month, $total_amount);
}
// there are aprox. 10 categories of payments which dont have a common pattern so they must be treated separately
// these functions below work similarly to storePaymentCategory1()
if($payment_category->id == '2'){
$this->storePaymentCategory2($apartment, $payment_category, $month, $total_amount);
}
if($payment_category->id == '3'){
$this->storePaymentCategory3($apartment, $payment_category, $month, $total_amount);
}
if($payment_category->id == '4'){
$this->storePaymentCategory4($apartment, $payment_category, $month, $total_amount);
}
// ...
// NEXT I HAVE A FUNCTION FOR GENERATING THE PDF RECEIPT
// NEXT I HAVE ANOTHER FUNCTION TO GENERATE THE INVOICE, IF THE APARTMENT REQUIRES ONE
}
}
Next I have the storeMainData() function:
$validator = Validator::make(Input::all(), MainData::$rules, MainData::$messages);
if($validator->passes()){
$check = MainData::where('month', $month)->where('apartament_id', $apartament->id)->first();
if(count($check)>0){
$store_main_data = $check;
}
else{
$store_main_data = new MainData;
}
//main data stuff (like apartment receipt data etc...)
$store_main_data->save();
}
else{
return Redirect::back()
->withErrors($validator);
}
Next I have the storePaymentCategory1() function (which is an example of how other payment categories work as well):
// NOW WE MUST APPLY: total amount stored in the payment category / apartment percentage = due payment for the apartment;
// identify the percentage
$percentage_type = $payment_category->percentage_id;
$percentage = Percentages::where('percentage_type_id', $percentage_type)->where('apartment_id', $apartment->id)->first();
// verify if the percentage type is applied to this apartment
$calculated_apartments = Percentages::where('percentage_type_id', $percentage_type)->get();
$calculated_apartments_array = [];
foreach($calculated_apartments as $calculated_apartment){
array_push($calculated_apartments_array, $calculated_apartment->apartment_id);
}
if(in_array($apartment->id, $calculated_apartments_array)){
$validator = Validator::make(Input::all(), DuePayments::$rules, DuePayments::$messages);
if($validator->passes()){
$check = DuePayments::where('month', $month)->where('apartment_id', $apartment->id)->where('payment_category_id', $payment_category->id)->first();
if(count($check)>0){
$store_due_payment = $check;
}
else{
$store_due_payment = new DuePayments;
}
// store due payment stuff
$store_due_payment->save();
}
else{
return Redirect::back()
->withErrors($validator);
}
}

Symfony2 ->set doesn't update the database

I need to create a web application for a touristic agency which promotes auctions online of travels. I've been asked to create a code which allows a user to bid one time before the auction starts, in fact the auction can be seen online while it is not started yet and can receive only one bid from every bidder before it starts. When the auction starts the bidders are allowed to bid only one other time. I know, it's pretty odd. However, with my code I can insert the first bid smoothly but when it comes to update the bid with a new and last price it simply doesn't update the data. I don't know why because it perfectly retrieves the object of the bid and the doctrine's names of each field are right. The fields' names are the following:
- setAmount (how much you bid which changes every time you bid),
- setFirstbid (which turns to 1 one you bid the first time),
- setLastBid (which turns to 1 when you bid the second and last time).
It simply won't update these three fields.
This is the code:
public function createAction(Request $request, ProductInfo $productInfo)
{
** I first get the user info **
$user_fos = $this->get("security.context")->getToken()->getUser();
$id_user = $user_fos->getId();
$id_product = $productInfo->getId();
$user = $this->getDoctrine()->getRepository("CasaUserBundle:User")->find($id_user);
$em = $this->getDoctrine()->getManager();
** I set the bid **
$entity = new BidsInfo();
** I get info about the auction I want to bid for **
$
entity_prod =$this->getDoctrine()->getRepository("ReverseAuctionReverseAuctionBundle:ProductInfo")->find($productInfo);
$form = $this->createCreateForm($entity, $productInfo);
$form->handleRequest($request);
$dati = $form->getData();
** With the following function I verify how many times the bidder has bid and then if he is allowed to bid **
$how_many_bids = $this->getDoctrine()->getRepository("ReverseAuctionReverseAuctionBundle:BidsInfo")->CheckPermit($id_product, $id_user, $dati->getBAmount());
** I check the form validation and if I'm allowed to bid **
if ($form->isValid() && $how_many_bids[0]!=0) {
try{
$em->beginTransaction();
** this is the first bid which works pretty well **
if($how_many_bids[0] == 1){
$entity->setUserInfo($user);
** How much he bids **
$entity->setBAmount($dati->bAmount);
$entity->setProductInfo($productInfo);
$entity->setBidfirst(1);
$entity->setBidfinal(0);
$em->persist($entity);
** the following is the second bid which doesn't work with the updating process **
}else if($how_many_bids[0] == 2){
** I get the bidder's object **
$bid_getid = $em->getRepository("ReverseAuctionReverseAuctionBundle:BidsInfo")->findOneBy(array('UserInfo' => $id_user, 'ProductInfo' => $id_product));
** It doesn't store the following data **
$biduser->setBAmount($dati->bAmount);
$biduser->setBidfirst(1);
$biduser->setBidfinal(1);
$em->flush();
$em->commit();
}else{
throw new Exception("No data found!");
}
$em->flush();
$em->commit();
} catch (Exception $ex) {
$em->rollback();
throw $ex;
}
Do you have any clue why it doesn't store data when I update the bid? It seems setBAmount, setBidFinal and setBidFirst don't work when I update them.

Categories