Laravel 5.3 DB:transaction is committing even some queries failed - laravel

I have a User model for basic user info, then a Role model and RoleUser model to associate roles with user. On user edit form, additional role can be added to that user. So, here two DB operations are done within a DB::transaction,
1) Update User info into User model
2) Add role to user
The issue is, if "Add role to user" fails, it doesn't Rollback changes in "User" model which already updated successfully.
Here is my sample code-
In Controller:
$response =
DB::transaction(function() use($user_data, $input) {
//Update changes in basic user info using "User" Model
$response = User::updateUser($user_data['user']);
if ($response->status===FALSE) {//not updated
return $response;
}
if (!empty($user_data['roles'])) {
$roles = [];
foreach ($user_data['roles'] as $role) {
$roles[] = ['role_id' => $role, 'user_id' => $user_data['user']['id'], 'created_by' => $this->curr_user->id, 'updated_by' => $this->curr_user->id];
}
//Add new roles to the user using "RoleUser" Model
$response3 = RoleUser::createRoleUser($roles);
if ($response3->status===FALSE) {//failed to add
return $response3;
}
}
return $response;
}, 5);
//source of createRoleUser method in RoleUser model
try {
DB::table($table)->where('id', $id)->update($changes);
} catch (\Illuminate\Database\QueryException $qe) {
return (object) ['status' => FALSE, 'error' => $qe->errorInfo];
} catch (\Exception $e) {
return (object) ['status' => FALSE, 'error' => [$e->getCode(), 'non-DB', $e->getMessage()]];
}
return (object) ['status' => TRUE, 'data' => $changes + ['id' => $id]];
//source of createRoleUser method in RoleUser model
try {
$new_rec_id = DB::table('role_users)->insertGetId($new_data);
$new_rec = FALSE;
if ($new_rec_id) {
$new_rec = DB::table($table)->where('id', $new_rec_id)->first();
}
} catch (\Illuminate\Database\QueryException $qe) {
return (object) ['status' => FALSE, 'error' => $qe->errorInfo];
} catch (\Exception $e) {
return (object) ['status' => FALSE, 'error' => [$e->getCode(), 'non-DB', $e->getMessage()]];
}
return (object) ['status' => TRUE, 'data' => $new_rec];

You have to throw an exception from within the transaction closure in order for the transaction to trigger the rollback. If no exception is thrown, the transaction will commit.
Keeping this in mind, that means the call to the transaction function needs to be wrapped in a try/catch, as the code that handles the rollback will rethrow the exception after the rollback for your application code to handle.
So, your code would look something like:
try {
$response = DB::transaction(function() use($user_data, $input) {
//Update changes in basic user info using "User" Model
$response = User::updateUser($user_data['user']);
if ($response->status===FALSE) {//not updated
// throw exception to trigger rollback
throw new \Exception($response->error);
}
if (!empty($user_data['roles'])) {
$roles = [];
foreach ($user_data['roles'] as $role) {
$roles[] = ['role_id' => $role, 'user_id' => $user_data['user']['id'], 'created_by' => $this->curr_user->id, 'updated_by' => $this->curr_user->id];
}
//Add new roles to the user using "RoleUser" Model
$response3 = RoleUser::createRoleUser($roles);
if ($response3->status===FALSE) {//failed to add
// throw exception to trigger rollback
throw new \Exception($response3->error);
}
}
// return without exception to trigger commit
return $response;
}, 5);
} catch (\Exception $e) {
echo 'uh oh: '.$e->getMessage();
}

Related

add a number with the current number in database with Laravel Eloquent

I have this code to update a payment record if it already exists, if not, just create new one, and it is working ..... but I need to add a new amount to the existing amount for the update case:
try {
Payment::updateOrCreate([
'user_id' => $request->users,
], [
'amount' => $request->amount,
'date' => $mytime,
'number' => $number++,
]);
} catch (\Exception $ex) {
return redirect()->back()->with('status', 'you cannot insert this record');
}
What about using firstOrNew and doing something like this?
https://laravel.com/docs/9.x/eloquent#retrieving-or-creating-models
try {
$payment = Payment::firstOrNew([
'user_id' => $request->users,
], [
'amount' => $request->amount,
'date' => $mytime,
'number' => $number++,
]);
if ($payment->id) {
$payment->amount = $payment->amount + $new_amount;
}
$payment->save();
} catch (\Exception $ex) {
return redirect()->back()->with('status', 'you cannot insert this record');
}
In case a model is found, you will have a payment instance id, then you manipulate the amount.
For new Payment the model instance doesn't have an id then you just save the model.
Note that the model returned by firstOrNew has not yet been persisted to the database. You will need to manually call the save method to persist it.

Axios returns error status code 500 when there is data present

I am using Laravel 8, VueJS and Axios for my application then every time I try to fetch all records from my database it returns an error with status code 500. Even though when fetching the data using Postman/Insomnia it returns the data without an error.
I tried to empty the table where it fetches the data the error disappears and it returns empty data with status code 200.
Store Module:
import axios from 'axios'
export default {
namespaced: true,
state: {
courses: [],
teacher: '',
},
getters: {
allCourses(state) {
return state.courses
},
},
actions: {
async fetchAllCourses({ commit }) {
const response = await axios.get('teacher/course-management/list')
console.log(response.data.data)
commit('SET_COURSES', response.data.data)
}
},
mutations: {
SET_COURSES(state, courses) {
state.courses = courses
}
}
Controller:
public function fetchAllCourses() {
try {
$courses = Course::all()->sortBy('id');
$data = $courses->transform(function ($course) {
// ! Get teacher id
$teacherId = $this->user->teacher->id;
// ! Get teacher name by id
$teacherName = $this->getTeacherName($teacherId);
return [
'id' => $course->id,
'teacher_id' => $course->teacher_id,
'teacher' => $teacherName,
'section' => $course->section,
'code' => $course->code,
'status' => $course->status,
'image' => $course->image,
];
});
return $this->success('Request success', $data);
} catch (\Exception $e) {
return $this->error($e->getMessage(), $e->getCode());
}
}
Problem solved.
public function fetchAllCourses() {
try {
$courses = Course::all()->sortBy('id');
$data = $courses->transform(function ($course) {
return [
'id' => $course->id,
'teacher_id' => $course->teacher_id,
'teacher' => $this->getTeacherName($course->teacher_id),
'section' => $course->section,
'code' => $course->code,
'status' => $course->status,
'image' => $course->image,
];
});
return $this->success('Request success', $data);
} catch (\Exception $e) {
return $this->error($e->getMessage(), $e->getCode());
}
}

Missing param for for named route after successful api call

I have a Settings component where a user can update their informations, after the update is successful I get in the console missing param for named route and all the inputs that should contain user data are empty, and when I try accessing the settings page it redirects to the home page even though all the data needed for it to work is still in the component's data.
This is the component's method to that commits an action in vuex store
updateUser(){
const fd = new FormData()
//check if avatar is of type file
if ('File' in window && this.avatar instanceof File){
fd.append('avatar', this.avatar, this.avatar.name)
}
fd.append('_method', 'PUT')
fd.append('city_id', this.userData? this.userData.city.id:this.city)
fd.append('oldPassword', this.oldPassword)
fd.append('newPassword', this.newPassword)
this.$store.dispatch('User/update', fd)
.then(() =>{
this.success = this.user.user.message
}).catch(err => {
this.error = err.response.data.message
})
}
the above method triggers this vuex action
update({commit}, user){
return axios.post(`/api/${user.name}/update`, user)
.then(( {data} ) => {
commit('UPDATE_USER_DATA', data)
})
}
And here's the UPDATE_USER_DATA mutation
UPDATE_USER_DATA(state, userData){
let user = localStorage.getItem('user')
const data = JSON.parse(user)
state.user.user = userData
data['user'] = userData
localStorage.setItem('user', JSON.stringify(data))
}
This is the laravel method that's called
public function update(User $user, Request $request)
{
$attributes = [];
if(request('city_id') && $request['city_id'] !== null){
$attributes['city_id'] = $request['city_id'];
}
if (request('oldPassword') && Hash::check($request['oldPassword'], auth()->user()->getAuthPassword())
){
if(request('newPassword')) {
$attributes['password'] = Hash::make($request['newPassword']);
}
} else{
return response()->json([
'message' => "L'ancien mot de passe est incorrecte.",
'user' => $user
], 400);
}
if (request('avatar')) {
$attributes['avatar'] = request('avatar')->store('avatars');
}
// This is where I have the issue
if (!empty($attributes)){
if($user->update($attributes)){
return response()->json([
'user' => $user,
'message' => 'Votre compte a été modifié avec succés'
], 200);
}
}
return response()->json([
'message' => "Votre compte n'a pas été modifié, réessayez plus tard",
'user' => $user
], 400);
}

Auth::attempt() Manually Authenticating Users based on NULL condition | Laravel | Passport

I am creating an application based on Laravel 5.8. I want to manually authentication users based on some checks, But these checks or fields have some null values or not null values.
I follow the official documentation Link
Instead of checking like this
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// The user is active, not suspended, and exists.
}
I want to check if some fields that are not null like
if (Auth::attempt(['email' => $email, 'password' => $password, 'activate_on' => 'SomeDateTimeValue Or Not Null' ])) {
}
So it means if the user has some activate_on fields value which should not Null then the Auth::attempt should return true otherwise false.
You can do it by adding your implementation of the UserProvider interface, but that's a lot of work.
I think the easiest way is to do it in two steps.
// first get the user by email
$user = User::whereEmail($email)->first();
if($user->activate_on && Auth::attempt(['email' => $email, 'password' => $password])
{
// logged in
}
I do not believe what you are asking is directly possible. If you take a look at the retrieveByCredentials() method which is called during the attempt() process, the query builder is only set up to accept a value where($key, $value) or an array of values whereIn($key, $value) to conditionally query the user.
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('password', $credentials))) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->newModelQuery();
foreach ($credentials as $key => $value) {
if (Str::contains($key, 'password')) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
return $query->first();
}
EDIT:
Sample helper function:
if (!function_exists('attempt')) {
function attempt($credentials, $dates = [], $remember = false)
{
$user = User::where('email',$credentials['email'])->first();
// if a date is null return false
foreach ((array)$dates as $date) {
if (is_null($user->{$date})) {
return false;
}
}
return Auth::attempt($credentials, $remember);
}
}
Usage:
// single date
if (attempt(['email' => $email, 'password' => $password],'activate_on')) {
// ...
}
// array of dates
if (attempt(['email' => $email, 'password' => $password],['activate_on','approve_on'])) {
// ...
}
// no date
if (attempt(['email' => $email, 'password' => $password])) {
// ...
}

How do I handle route exception not defined?

I created dynamic sidebar menu and when I try to insert new menu I am getting error message Route [nameroute] is not defined. How do I handle this error with try catch ?
This is my controller file.
DB::beginTransaction();
try
{
$insert = AppMenu::insertGetId([
'description' => $request->description,
'menu_url' => $request->menu_url ? $request->menu_url:null,
'menu_alias' => $request->menu_alias ? $request->menu_alias:null,
'ismenu' => $request->ismenu,
'parent' => $request->parent ? $request->parent:null,
'menu_icon' => $request->menu_icon,
'menu_order' => $request->menu_order
]);
DB::table('appmenu_role')->insert([
'appmenu_id' => $insert,
'role_id' => $role
]);
}
catch (\InvalidArgumentException $e)
{
return Redirect::back()->with('infoMessage', "Route not defined. ");
}
DB::commit();
Session::flash('successMessage', 'Menu Created');
return Redirect('menu');
You should use Exception class to catch any kind of exception.
catch (\Exception $e)
{
return Redirect::back()->with('infoMessage', "Route not defined.");
}

Resources