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

89 Views Asked by At

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.

3

There are 3 best solutions below

2
miken32 On BEST ANSWER

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.

4
designatedcoder On

Laravel has conventions that you would need to follow to get this to work. Even though you are using owner_id I don't see an owners table, it is looking for user_id on the users table. You're also calling in your store function:

auth()->user()->projects()->create($validatedData);

This is also looking for the auth user's id and auth is only associated with the authenticated user, not any other model.

If you really need and owner_id field, you can create an owners table and reference your user on that before you can reference it on the projects table from the owners. Then on your store functions you can find the owner by id and then store but also since this isn't authenticated from the user model you'll have to either make sure owner_id is in your fillable properties on your models or you can use guarded instead(I never recommend guarded when you're learning).

Keep in mind, migrations need to be in order so have your main migrations listed before your relations tables. Users, then owners, then projects.

0
Abdulla Nilam On

Usually this should do the job. auth()->user()->projects()->create($validatedData);. In your case, it fails.

You can try one more alternative way to set this value.

In the model

protected static function boot()
{
    parent::boot();

    static::creating(function ($project) {
        if (empty($project->owner_id)) {
            $project->owner_id = auth()->id();
        }
    });
}

Supported methods: creating, created, updating, updated, saving, saved, deleting, and deleted


Note: booted introduced in Laravel 8, defines code that should be executed after the model has booted

More to read