Hey guys I’m new to Laravel.
I have one user in my database with name, username and password, now I want to create a profile for this user through a form in my application, where the user can submit a profile image, description and url. I also would like the user to be able to edit and submit changes after. How can I do that?
My routes in web.php are:
Route::get('/users/{user}/edit', [UserController::class, 'edit']); Route::patch('/users/{user}', [UserController::class, 'update']);
My form in edit.blade.php file is as follow:
<form action="/users/{{ $user->id }}" enctype="multipart/form-data" method="post"> @csrf @method('PATCH') <div class="form-group"> <label for="description" class="edit_description_label">Description</label> <div class="edit_description_div"> <input id="description" type="text" class="form-control @error('description') is-invalid @enderror" name="description" value="{{ old('description') ?? $user->profile->description ?? '' }}" autocomplete="description" autofocus> @error('description') <div class="invalid-feedback-div"> <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> </div> @enderror </div> </div> <div class="form-group"> <label for="url" class="edit_title_label">URL</label> <div class="edit_url_div"> <input id="url" type="text" class="form-control @error('url') is-invalid @enderror" name="url" value="{{ $user->profile->url ?? '' }}" autocomplete="url" autofocus> @error('url') <div class="invalid-feedback-div"> <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> </div> @enderror </div> </div> <div class="create_post_image_div"> <label for="image" class="create_image_label">Profile Image</label> <input type="file" class="form-control-file" id="image" name="image"> @error('image') <div class="invalid-feedback-div"> <strong>{{ $message }}</strong> </div> @enderror <div class="create_post_btn_div"> <button class="create_post_btn">Save Profile</button> </div> </div> </form>
And finally, my methods in UserController are:
public function show(User $user) { return view('user.profile', ['user' => $user]); } public function edit(User $user) { return view('user.edit', ['user' => $user]); } public function update(Request $request, User $user) { $data = request()->validate([ 'description' => '', 'url' => '', 'image' => '', ]); $user->profile->update($data); return redirect('/users/{$user->id}'); }
To also give better context, the profile description, url and image columns are not in my User Model, but rather in my Profile Model. I have defined those relationships, so to allow for better understanding here’s the code for the following files:
This is the code in my User.php file:
<?php namespace AppModels; use IlluminateContractsAuthMustVerifyEmail; use IlluminateDatabaseEloquentFactoriesHasFactory; use IlluminateFoundationAuthUser as Authenticatable; use IlluminateNotificationsNotifiable; class User extends Authenticatable { use HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'username', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; public function profile() { return $this->hasOne(Profile::class); } }
This is the Users Table file:
<?php use IlluminateDatabaseMigrationsMigration; use IlluminateDatabaseSchemaBlueprint; use IlluminateSupportFacadesSchema; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('username')->unique(); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } }
This is the Profile.php file:
<?php namespace AppModels; use IlluminateDatabaseEloquentFactoriesHasFactory; use IlluminateDatabaseEloquentModel; class Profile extends Model { use HasFactory; protected $guarded = []; public function user() { return $this->belongsTo(User::class); } }
And this is the Profiles Table file:
<?php use IlluminateDatabaseMigrationsMigration; use IlluminateDatabaseSchemaBlueprint; use IlluminateSupportFacadesSchema; class CreateProfilesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('profiles', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('user_id'); $table->string('description')->nullable(); $table->string('url')->nullable(); $table->string('image')->nullable(); $table->timestamps(); $table->index('user_id'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('profiles'); } }
Advertisement
Answer
The
save
method may also be used to update models that already exist in the database. To update a model, you should retrieve it and set any attributes you wish to update. Then, you should call the model’ssave
method.
use AppModelsProfile; use IlluminateHttpUploadedFile; use IlluminateSupportFacadesStorage; use IlluminateHttpRequest; public function updateProfile($user) { $data = request()->validate($this->validationBag()); $profile = Profile::where('user_id', $user)->first(); if (isset($data['image'])) { $this->updateProfileImage($data['image'], $profile); } $profile->description = $data['description']; $profile->url = $data['url']; $profile->save(); return redirect("/users/$user"); } public function createProfile($user) { $data = request()->validate($this->validationBag()); $profile = Profile::create([ 'user_id' => $user, 'description' => $data['description'], 'url' => $data['url'], ]); if (isset($data['image'])) { $this->updateProfileImage($data['image'], $profile); } return redirect("/users/$user"); } private function updateProfileImage(UploadedFile $image, Profile $profile) { tap($profile->image, function ($previous) use ($image, $profile) { $profile->forceFill([ 'image' => $image->storePublicly( 'profile-photos', ['disk' => 'public'] ), ])->save(); if ($previous) { Storage::disk('public')->delete($previous); } }); } private function validationBag() { return [ 'description' => '', 'url' => '', 'image' => '', ]; }
Assumed routes.
Route::put('/users/{user}/profile', [UserController::class, 'updateProfile'])->name('update.user.profile'); Route::post('/users/{user}/profile', [UserController::class, 'createProfile']); Route::get('/users/{user}', [UserController::class, 'index']);
Edit your <form>
action
attribute to:
<form action="{{'/users/'. $user->id. '/profile'}}" enctype="multipart/form-data" method="post">
I hope you have this route /users/{$user}
defined in your routes/web.php
file.
Add a $fillable
member variable to AppModelsProfile
model.i.e:
protected $fillable = ["user_id", "description", "url", "image"];
Not really related to the question, but you may also want to add a foreign key constraint to the CreateProfilesTable
migration. i.e:
$table->foreignId("user_id")->constrained();
Since it’s a one-to-one relationship, it also makes sense to add a unique key constraint on user_id
column in ‘profiles table ‘(CreateProfilesTable
migration). i.e:
$table->string('user_id')->unique();