Sylar
Sylar

Reputation: 12082

Laravel Many to Many Relationship to join two models

I struggle to get this right with Laravel 5. However, in Rails, I can easily do this:

A User can be a part many Teams. We know that tables User and Team could not have a relation id so I wont post those schema here but instead I have a TeamUser table:

Schema::create('team_users', function (Blueprint $table) {
    $table->increments('id');
    $table->integer('user_id');
    $table->integer('team_id');
    $table->timestamps();
});

TeamUser.php:

public function user()
{
  return $this->belongsTo(User::class);
}

public function team()
{
  return $this->belongsTo(Team::class);
}

Team.php

public function users()
{
  return $this->hasManyThrough(User::class, TeamUser::class);
}

User.php

public function teams()
{
  return $this->hasManyThrough(Team::class, TeamUser::class);
}

It seems as if laravel's doc and other tutorials I have seen are different. Im new to Laravel, again, and I need this setup.

Tinker:

$tu = App\TeamUser::create([ 'user_id' => 1, 'team_id' => 1 ]);

Illuminate\Database\Eloquent\MassAssignmentException with message 'user_id'

Expected:

$tu->users // is_array($tu) to be true

Could someone please explain what is wrong here? Thanks much.

Upvotes: 0

Views: 2488

Answers (4)

JJWesterkamp
JJWesterkamp

Reputation: 7916

As far as I know, hasManyThrough() only substitutes for chaining 2 hasMany relationships. In your case we have to deal with a single many-to-many relationship, which is defined by using belongsToMany() on both models involved.

For that to work you should create a join-table. In Laravel, the naming convention for join tables is the names of both models involved, singular, snake_cased, and in alphabetical order, and separated by a _. In this case the table name would be team_user.

You'll want to create a migration through the artisan CLI:

$ php artisan make:migration create_team_user_pivot_table

And put the contents below inside the up() and down() methods of the new class that was created for you in YOUR_PROJECT/database/migrations/.

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('team_user', function (Blueprint $table) {
        $table->integer('team_id')->unsigned()->index();
        $table->foreign('team_id')->references('id')->on('teams')->onDelete('cascade');
        $table->integer('user_id')->unsigned()->index();
        $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        $table->primary(['team_id', 'user_id']);
    });
}

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    Schema::drop('team_user');
}

When all set you can run the new migration with the following console command: $ php artisan migrate

Then, in your Team model:

public function users()
{
    return $this->belongsToMany(User::class);
}

and in your User model:

public function teams()
{
    return $this->belongsToMany(Team::class);
}

This should be all you need to get the relations working.

Note that nothing stops you from creating an extra model called TeamUser to access your pivot/join table directly. You do have to specify the table name in that model to be team_user, since Eloquent assumes the table name to be the plural snake-cased version of the model name: team_users. This is as easy as:

protected $table = 'team_user';

If the sole purpose of the extra table is storing relations, however, I can not see why defining a Model class for that table can be considered useful. Laravel comes with a bunch of extra features that let you define and access additional columns on join tables through relationships.

(scroll down a bit to Saving Additional Data On A Pivot Table)

Upvotes: 0

sushibrain
sushibrain

Reputation: 2780

What I do when working out Eloquent relationships is say them out loud:

  • A user can be part of many teams
  • A team can have many users

This says that you'll need a many-to-many relationship. So, what we do in our User.php:

public function teams() {
    return $this->belongsToMany(Team::class);
}

Then, in our Team.php, we do this;

public function users() {
    return $this->hasMany(Users::class);
}

This should automatically make a pivot table, and set you up to the point where you could request e.g. a users teams by doing: Users::find(1)->teams. Note that i didn't use parentheses because I want the result, not the query builder object.

Upvotes: 0

Alex Slipknot
Alex Slipknot

Reputation: 2533

You can implement many-to-many relation with middle table by using belongsToMany

For example in Team.php:

public function users() {
    return $this->belongsToMany(User::class, 'team_user', 'team_id', 'user_id');
}

In that case you don't need to create model TeamUser

Upvotes: 1

jakub wrona
jakub wrona

Reputation: 2254

To avoid seeing the pasted error message you would have to add the following line to your TeamUser.php class:

protected $fillable = ['user_id', 'team_id'];

Upvotes: 0

Related Questions