Laravel avoid duplication between existing data and new inputs - laravel

I have an edit form where it shows users, sub-users and I need to make my update method the way that it can update existed sub-users as well as add new ones.
Logic
User can have a maximum of 5 sub-users
Let's assume my user already have 2 sub-users s/he can add up to 3 more
I want to be able to update those 2 that are existed already
I want to be able to add those 3 new sub-users
Code
Here is what I have currently. Code is commented for better understanding
public function subUsersUpdate(Request $request, $id) {
$will = Will::where('id', $id)->where('user_id', Auth::id())->first();
// main user
$user = User::where('id', $will->user_id)->first();
// making custom emails for new users like (admin1#example.com)
// admin is email name of main user, originally (admin#gmail.com)
// 1~5 are added to names (separate those 5 sub-users)
// example.com is website domain
$userMail = substr($user->email, 0, strpos($user->email, '#'));
$websiteName = env('DOMAIN_NAME');
// here is what I get from edit form including 2 existed sub-users and 3 new sub-users
foreach($request->input('subs') as $index => $sub) {
// trying to separate existed sub-users from new ones
$existedUser = User::where('name', $sub)->where('user_id', $user->id)->first();
if($existedUser) {
// if already existed just update the name
$existedUser->update(['name' => $sub]);
} else {
// if new add them as new sub-user
$num = $index+1;
$password = str_random(15);
User::create([
'name' => $sub,
'email' => $userMail . $num ."#" . $websiteName,
'password' => Hash::make($password),
'user_id' => $user->id,
]);
}
}
return redirect()->back();
}
Error
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'admin4#example.com' for key 'users_email_unique'
It appears that my code works in order to separate old and new sub-users but the problem is it makes the same email for new-users as old ones based on $num provided in my foreach. I need somehow avoid that duplication.
Any suggestion?
Update
What I did
I've added hidden input in my form which has sub-user email as value (if user existed) and if user is newly added it is null.
Then I merged those emails array and names array into one
Finally I've validate if the email field is null or not in order to update or create new sub-user.
Issue
Issue is name of all sub-users will be same as last input (sub-user 5), regardless of user being newly created or updated all 5 get same name!
public function subUsersUpdate(Request $request, $id) {
$will = Will::where('id', $id)->where('user_id', Auth::id())->first();
$user = User::where('id', $will->user_id)->first();
$userMail = substr($user->email, 0, strpos($user->email, '#'));
$websiteName = env('DOMAIN_NAME');
//Changed part
$mails = $request->input('mails');
$names = $request->input('subs');
// created array of data
$finalInputs = [];
foreach($mails as $index => $mmm) {
foreach($names as $index2 => $nnn) {
$finalInputs[$index] = ['email' => $mmm, 'name' => $nnn];
}
}
foreach($finalInputs as $index => $sub) {
if(!empty($sub['email'])) {
$existedUser = User::where('email', $sub['email'])->where('user_id', $user->id)->first();
$existedUser->update(['name' => $sub['name']]);
// $userMail2 = substr($sub['email'], 0, strpos($sub['email'], '#'));
} else {
if($sub['name'] != null) {
$num = $index+1;
$password = str_random(15);
User::create([
'name' => $sub['name'],
'email' => $userMail . $num ."#" . $websiteName,
'password' => Hash::make($password),
'user_id' => $user->id,
]);
}
}
}
// end of changed parts
}

Using updateOrCreate()
updateOrCreate() can handle conditional updating or creating of records for you.
This could be what you are asking, hope you get the idea:
foreach($finalInputs as $index => $sub) {
$pwHash = Hash::make( str_random(15) );
$num = $index+1;
$search = [
'user_id' => $user->id,
'email' => $userMail . $num ."#" . $websiteName,
];
$update = [
'name' => $sub['name'],
];
// Only add 'password' for new users
if (empty($sub['email']))) {
$update['password'] = $pwHash;
}
User::updateOrCreate(
$search,
$update
);
}
This will update or create users with the given email address and user-id and create if they don't exist. I don't know how your form works in detail, but I think you can adjust to your exact use-case.

Related

Adding data to Database in Laravel along with validated data and non validated data

I have a form in my view and on submit the fields are validated from the controller and I also need to add some data to database which doesn't need any validation. So how can I store these validated data and non validated(non validated data is set in controller it is not submitted along with the form in the view) data in short code.
This is my Controller
public function postRegister($type, Request $request){
$message = "Succesfully Registered, Login to access your Account";
$validatedData = $request->validate([
'name' => 'required|max:100',
'legal_type' => 'required|max:25',
'phonenumber' => 'required|max:12',
'email' => 'required|max:45',
'linkedinprofile' => 'required|max:250',
'address' => 'required:max:350',
'country' => 'required|max:15',
'email' => 'required|max:45',
'password' => 'required|max:120',
'terms' => 'required'
]);
LegalProfessional::create($validatedData);
// $lp = new LegalProfessional();
// $lp->lp_name = $request->input('name');
// $lp->lp_type = $request->input('legal_type');
// $lp->lp_phone = $request->input('phonenumber');
// $lp->lp_email = $request->input('email');
// $lp->lp_linkedin_profile = $request->input('linkedinprofile');
// $lp->lp_address = $request->input('address');
// $lp->country = $request->input('country');
// $lp->lp_membership_type = 'Premium';
// //$lp->lp_rfqs = $request->input('name');
// $lp->lp_username = $request->input('email');
// $lp->password = $request->input('password');
// $lp->save();
Session::flash('message', 'Successfully Registered!');
return redirect('/login');
In PHP you can add associative arrays together with the + operator. This will basicly add you extra fields to the associative array of $validatedData.
LegalProfessional::create(
$validatedData +
[
'lp_membership_type' => 'premium',
]
);
This is in my opinion the easiest and prettiest way to achieve what you want.
Edit
Remove terms, use PHP built in function unset(), that remove items if passed an arrray element.
unset($validatedData['terms']);
LegalProfessional::create(
...
To set a hashed password, just overwrite the $validatedData field.
$validatedData['password'] = Hash::make($request->input('password'));
LegalProfessional::create(
...

Laravel Socialite Facebook images are incorrect

I have a strange problem which I cannot figure out. I am using Laravel Socialite to allow login with Facebook account, there is a lot of users complaining that the profile image displayed on their account is incorrect, however the name etc is all fine.
It makes no sense to me as the name and the profile pic are pulled directly from FB at the same time.
public function socialUser(ProviderUser $providerUser)
{
$account = SocialFacebookAccount::whereProvider('facebook')->whereProviderUserId($providerUser->getId())->first();
if ($account) {
return $account->user;
} else {
$account = new SocialFacebookAccount([
'provider_user_id' => $providerUser->getId(),
'provider' => 'facebook'
]);
$user = User::whereEmail( $providerUser->getId() )->first(); // changed from getEmail to getId as FB doesnt always give an email
if (!$user) {
$profilePicName = md5(rand(1,10000)) . ".jpg";
$contents = file_get_contents( $providerUser->avatar_original );
Storage::disk('local')->put( 'profiles/' . $profilePicName, $contents );
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
'profile_path' => $profilePicName
]);
}
$account->user()->associate($user);
$account->save();
return $user;
}
}
Instead of using md5, you can use the UUID generator. One of the reasons for this problem because md5 generates the same hash for the same input. for instance, when you pass md5(111) it'll generate 698d51a19d8a121ce581499d7b701668. so if for a different user you generate the same hash key, the latest image profile will take a place and the former will replace by the latter.
Scenario:
User1:
$profilePicName = md5(111) . ".jpg";
$contents = file_get_contents( $providerUser->avatar_original );
Storage::disk('local')->put( 'profiles/' . $profilePicName, $contents );
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
'profile_path' => $profilePicName
]);
Scenario:
User2:
$profilePicName = md5(111) . ".jpg";
$contents = file_get_contents( $providerUser->avatar_original );
Storage::disk('local')->put( 'profiles/' . $profilePicName, $contents );
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
'profile_path' => $profilePicName
]);
Now they both have 698d51a19d8a121ce581499d7b701668.jpg saved in their profile. And in this case the first user profile will be the same as the second user image profile.

How to write TDD code for user profile update in Laravel

I want to create a TDD first before using my function in the app.
I have already created the update function, update works but before that i want a test case running. So i have created this test case.
/** #test */
public function updateUser()
{
$this->withExceptionHandling();
//creating a user
$user = factory(User::class)->create();
//passing updating values
$response = $this->put('users/update_profile', [
'name' => 'name123',
'phoneno' => 9842345562,
'address' => 'newwwww'
]);
$this->assertEquals('name123', User::first()->name);
$this->assertEquals(9842345562, User::first()->phoneno);
$this->assertEquals('newwwww', User::first()->address);
}
//update function
public function update(UpdateProfileRequest $request)
{
$user = auth()->user();
$user->update([
'name' => $request->name,
'phoneno' => $request->phoneno,
'address' => $request->address
]);
session()->flash('success', 'User Proifile Updated');
return redirect(route('users.view-profile'));
}
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-'name123'
+'Tad Predovic'
Only getting this error.
You should not rely on User::first() as your first record may not the one you just created. Instead refresh the model you already have to get the updated values from the DB after your new values are set. You can use $user-refresh() before your assertions
/** #test */
public function updateUser() {
$this->withExceptionHandling();
//creating a user
$user = factory(User::class)->create();
//signing in as the new user
$this->actingAs($user);
//passing updating values
$response = $this->put('users/update_profile', [
'name' => 'name123',
'phoneno' => 9842345562,
'address' => 'newwwww'
]);
//Get new values
$user->refresh();
$this->assertEquals('name123', $user->name);
$this->assertEquals(9842345562, $user->phoneno);
$this->assertEquals('newwwww', $user->address);
}

laravel DB update get changes column

i want to save log of changes when i update something on the database.
there is elegant way to get the column that will be updated (just if there is change).
i want to save the old column value in log..
for example:
$updateUser = DB::table('users')->where('id','1')->update(array('email' => 'new#email.com', 'name' => 'my new name'));
from this i want to get back the old email was in database (if changed) and the old name (again, only if changed)
thanks!
As others have mentioned, Eloquent is a great way to go if using Laravel. Then you can tap directly into Laravel's events using Observers. I have used a method very similar to what is below. Of course, you would need to set up Models for User and AuditLog.
See more info regarding Observers.
https://laravel.com/docs/5.8/eloquent#observers
In Controller Method
$user = User::find(1);
$user->update([
'email' => 'new#email.com',
'name' => 'my new name'
]);
App/Providers/EventServiceProvider.php
class EventServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
User::observe(UserObserver::class);
}
}
App/Observers/UserObserver.php
class UserObserver
{
/**
* The attributes to exclude from logging.
*
* #var array
*/
protected $except = [
'created_at',
'updated_at'
];
/**
* The attributes to mask.
*
* #var array
*/
protected $masked = [
'password',
];
/**
* Listen for model saved event.
*
* #var array
*/
public function saved($model)
{
// search for changes
foreach ($model->getChanges() as $key => $new_value) {
// get original value
$old_value = $model->getOriginal($key);
// skip type NULL with empty fields
if ($old_value === '' && $new_value === null) {
continue;
}
// attribute not excluded and values are different
if (!in_array($key, $this->except) && $new_value !== $old_value) {
// mask designated fields
if (in_array($key, $this->masked)) {
$old_value = '********';
$new_value = '********';
}
// create audit log
AuditLog::create([
'user_id' => auth()->user()->id,
'model_id' => $model->id,
'model' => (new \ReflectionClass($model))->getShortName(),
'action' => 'update',
'environment' => config('app.env'),
'attribute' => $key,
'old_value' => $old_value,
'new_value' => $new_value,
]);
}
}
}
}
I hope this helps!
EDIT: See comment regarding update.
I will suggest 2 options:
1) to use the Eloquent model on every changes,
and then to use the existing methods like :
model->isDirty()
model->getChanges()
you can implement it on the model life cycle of updating / updated events listeners
more information and example you can see here:
https://laravel.com/docs/5.8/events
https://medium.com/#JinoAntony/10-hidden-laravel-eloquent-features-you-may-not-know-efc8ccc58d9e
https://laravel.com/api/5.3/Illuminate/Database/Eloquent/Model.html
2) if you want to log changes even if you are running regular queries and not only via model life cycle,
you can use MySql Triggers on every table updates and then to check OLD vs NEW and insert directly to the log changes db
more information you can find here:
https://dev.mysql.com/doc/refman/8.0/en/trigger-syntax.html
MySQL Trigger after update only if row has changed
Why not just something like this:
$changeArr = ['email' => 'new#email.com', 'name' => 'my new name'];
$id = 1;
$table = 'users';
foreach($changeArr as $key => $value){
DB::table('updateTable')->insert(['table' => $table, 'id' => $id, 'col' => $key, 'oldVal' => $value]);
}
$updateItem = DB::table($table)->where('id', $id)->update($changeArr);
Check for the changed values and update accordingly, saving the old values to log table if changed
$newData = ['email' => 'new#email.com', 'name' => 'my new name'];
$user = App\User::find(1);
$log = [];
if ($user->email != $newData['email']) {
$log['user_id'] = $user->id;
$log['email'] = $user->email;
$user->email = $newData['email'];
} elseif ($user->name != $newData['name']) {
$log['name'] = $user->name;
$user->name = $newData['name'];
$logged = DB::table('log')->insert($log);
}
$updateUser = $user->save();
//try this. hpe it helps out:
function Update(Request $request, $id)
{
$dbrecord = DB::table('users')->where('id',$id)->first();
$oldemail = $dbrecord->email;
$oldname = $dbrecord->name;
if(($oldemail==$request->input('email'))&&($oldname==$request->input('name')))
{
//do nothing
}
elseif(($oldemail!=$request->input('email'))or($oldname!=$request->input('name')))
{
$updateUser = DB::table('users')->where('id',$id)->update(array('email' => $request->input('email'), 'name' => $request->input('name')));
if($updateUser)
{
DB::table('log')->where('id',$id)->insert(array('email' => $oldemail, 'name' => $oldname));
}
}
}

Update profile function

I have a function that check updates the users profile info. Currently, if I put |unique:users in the validator every time I try to update the profile info on the form it will not let me because a user (which is me) has my email. So I figured out the unique means that nobody, including the current user can have the email that is being updated.
So I need to compare the current auth email to the one in the database. If it matches then it is ok to update the profile info. I know this is simple but I am not sure how to implement it and if that is the right logic.
So where in this code would I post if (Auth::user()->email == $email){..update email...} http://laravel.io/bin/GylBV#6 Also, is that the right way to do this?
public function editProfileFormSubmit()
{
$msg = 'Successfully Updated';
$user_id = Auth::id();
$user = User::find($user_id);
$first_name = Input::get('first_name');
$last_name = Input::get('last_name');
$email = Input::get('email');
$phone_number = Input::get('phone_number');
$validator = Validator::make(Input::all(), array(
'email' => 'required|email',
'first_name' => 'required',
'last_name' => 'required',
'phone_number' => 'required'
));
if ($validator->fails()) {
return Redirect::route('edit-profile')
->withErrors($validator)
->withInput();
}else{
if(Input::hasFile('picture')){
$picture = Input::file('picture');
$type = $picture->getClientMimeType();
$full_image = Auth::id().'.'.$picture->getClientOriginalExtension();
if($type == 'image/png' || $type == 'image/jpg' || $type == 'image/jpeg'){
$upload_success = $picture->move(base_path().'/images/persons/',
$full_image);
if($upload_success) {
$user->picture = $full_image;
} else {
$msg = 'Failed to upload picture.';
}
}else{
$msg = 'Incorrect image format.';
}
}
$user->first_name = $first_name;
$user->last_name = $last_name;
$user->email = $email;
$user->phone_number = $phone_number;
$user->save();
return Redirect::route('invite')->with('global', $msg);
}
}
Worry not, Laravel has already considered this potential issue! If you take a look at the docs for the unique validation rule you'll see that it can take some extra parameters. As it happens, you can give it an id to ignore when looking at the unique constraint. So what you need to do is work out the id for the current model to update and pass that in. In the case of updating a logged-in user's profile it's made easy by Auth::id() as you already have in your code.
$rules = [
'email' => ['required', 'email', 'unique:users,email,'.Auth::id()],
'first_name' => ['required'],
// etc...
];
Obviously I chose to use the array syntax for validation rules there, but you can do the same with the pip syntax too. In a less specific system (create-or-add in a crud postSave type action) you can still do it by simply dong something like $model = Post::find($id) and then if $model is null you're creating and you just use 'unique' whereas if $model is not null, use 'unique:table,field,'.$model->getKey().

Resources