So the above answer from Sᴀᴍ Onᴇᴌᴀ already covers quite a few points.
I've worked with Laravel for the past 4 years, so I'll go a bit further in depth on how I'd approach this, including a few suggestions.
First you'll want to use RequestValidation as Sᴀᴍ Onᴇᴌᴀ indicated, this will help guard against invalid input. And make use of Laravel's own input validation logic (sends the user back to the form with error messages in your message bag)
Secondly you have a lot of logic on the User model related to an avatar. I'd suggest creating an Image / Avatar Model that handles the image part of it all, in the end you'd end up with an image_id/avatar_id on the User model as a relation. That way you can have all the logic that handles a file on your filesystem and the associated data. It also enables you (if you go for image) to reuse the logic in a different context.
And declutters your user model
Thirdly, as Sᴀᴍ Onᴇᴌᴀ indicated you do a lot of ->save()
calls.
Each call is a database round trip. And a costly action.
You can set each property seperately if you want and at the end of your logic you can call ->isDirty()
on your user model, if it returns true, data has changed from the database returned data, indicating an update should be done. At this point you could save.
I however would suggest building an update array of the data to update, and calling the ->update()
method with an array containing the key => value
pairs that you wish to update. You can check this data array for emptyness to avoid unneccessary updates.
I would always suggest having some form of validation when changing passwords, to make sure an unauthorized user can't simply change the password. (a simple enter current password would do here)
I'll refactor the code you had and try to leave it inside the existing method.
public function update(Request $request)
{
$user = Auth::user();
// Read up on the Laravel Request Validation for available rules and usages
$request->validate([
'username' => 'sometimes',
'email' => 'email|sometimes',
'wallet_address' => 'sometimes',
'about' => 'sometimes',
'password' => 'sometimes',
'current_password' => 'required_with:password',
'website' => 'sometimes',
'location' => 'sometimes',
]);
// $request->input() will return null if the given key is not found
// I use an array_filter here to filter out empty results, if you expect falsey values (like null to unset something, or 0/false/'') you should use a different approach
$update_data = array_filter([
'username' => $request->input('username'),
'email' => $request->input('email'),
'wallet_address' => $request->input('wallet_address'),
'about' => $request->input('about'),
'password' => $request->input('password'),
'website' => $request->input('website'),
'location' => $request->input('location'),
]);
if ($request->input('password')) {
// enter logic here to validate existing password
// if this logic fails, you should just return instead of saving anything
}
if (!empty($update_data)) {
// You can store the result to see if you successfully updated
$update_status = $user->update($update_data);
}
// Check if an file has been given
if ($request->hasFile('avatar')) {
// I'd suggest moving the image handling to a seperate method at least.
$this->handleImage($user, $request->file('avatar'));
}
if (!$update_status) {
return redirect()->route('edit_profile')->with('failure','Your account could not be updated.')
}
return redirect()->route('edit_profile')->with('success','Your account has been updated successfully.')
}
private function handleImage(User $user, UploadedFile $image)
{
// if the image is a seperate relation, you could delete the existing relation
// on the delete for the relation you could hook in a cleanup for the file.
if ($user->avatar_name) {
// You can add a "disk" to your filesystem configuration where you can set the default path.
Storage::delete('/public/profile-pics/' . $user->avatar_name);
}
$image_new_name = date('dmy_H_s_i') . '_' . $user->id . '_' . $image->getClientOriginalName();
$image->storeAs('profile-pics', $image_new_name, 'public');
// if you use a properly configured Storage::disk you can only store the name, and the disk will resolve where to look for the file
// Storing paths in the database binds your database to your filesystem structure making future changes more difficult to handle (think moving images to a seperate location, or a different server)
// Another point, if you expect many users, consider using a subdivision in your path (something like the first few digits of the user id or anything that'll let you break up the files into subdirectories), most systems do weird things once you deal with 200k+ files in a single directory.
$user->avatar_url = 'storage/profile-pics/' . $image_new_name;
$user->avatar_name = $image_new_name;
$user->save();
}