phplaraveleloquentforeign-keys

How do I automatically insert the auth user id when creating a new model with a foreign key in Laravel?


I have a 'projects' table in my Laravel project. The migration looks something like this:

Schema::create('projects', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('key')->nullable();
    $table->integer('bpm')->nullable();
    $table->boolean('is_collaborative')->default(false);
    $table->foreignId('owner_id')->constrained('users')->cascadeOnDelete();
    $table->timestamps();
});

I want the owner_id field to reference a user's id in order to know who the owner of the project is (since projects will be collaborative and multiple users will have access to them).

After creating the following method on my Project model:

public function owner(): BelongsTo
    {
        return $this->belongsTo(User::class, 'owner_id');
    }

And the following method on my User model:

public function projectsOwned(): HasMany
    {
        return $this->hasMany(Project::class, 'owner_id');
    }

I implemented the store function on my ProjectController:

public function store(Request $request)
    {
        // Validate the data input by the user.
        $validatedData = $request->validate([
            'title' => ['required', 'max:255'],
        ]);

        // Create a new project.
        auth()->user()->projects()->create($validatedData);

        return to_route('projects.index')->with('status', __('Project created successfully!'));
    }

The thing is, this function will throw the following exception: General error: 1364 Field 'owner_id' doesn't have a default value when executed.

What can I do to make Laravel automatically insert the authenticated user's id into the owner_id field without having to add owner_id to the $fillable array of the Project model?

PS: I also have a many-to-many relationship between users and projects to keep track of who has access to which projects. That's why I use the auth()->user()->projects() method when trying to store a new project in the database.


Solution

  • So your migrations and models should look like this (though I think they already do):

    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('name');
    });
    Schema::create('projects', function (Blueprint $table) {
        $table->id();
        $table->foreignId('owner_id')->constrained('users')->cascadeOnDelete();
        $table->string('title');
    });
    Schema::create('project_user', function (Blueprint $table) {
        $table->id();
        $table->foreignId('project_id')->constrained()->cascadeOnDelete();
        $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    });
    
    class User extends Model {
        public function projects(): BelongsToMany {
            return $this->belongsToMany(Project::class);
        }
        public function ownedProjects(): HasMany {
            return $this->hasMany(Project::class, 'owner_id');
        }
    }
    
    class Projects extends Model {
        protected $fillable = ['title', 'owner_id'];
    
        public function users(): BelongsToMany {
            return this->belongsToMany(User::class);
        }
        public function owner(): BelongsTo {
            return $this->belongsTo(User::class, 'owner_id');
        }
    }
    

    With that in place you can create new owned models with either of these statements:

    $project = auth()->user()
        ->ownedProjects()
        ->save(new Project(['title' => 'New project']));
    
    $project = auth()->user()
        ->ownedProjects()
        ->create(['title' => 'New project']);
    

    Or new associated models like this:

    $project = auth()->user()
        ->projects()
        ->save(new Project(['title' => 'New project', 'owner_id' => $owner_user_id]));
    
    $project = auth()->user()
        ->projects()
        ->create(['title' => 'New project', 'owner_id' => $owner_user_id]);
    

    You need to either use the ownedProjects relationship to create the new models or manually insert the owner, because the projects() relationship doesn't have any awareness of the owner_id column.

    See live demo here.