Tricks

Attaching records using the Repeater field and HasMany

Jul 17, 2023
Patrick Boivin
Admin panel, Form builder, FAQ

Context

Let's say you want to associate Players to Teams in your application. After reading the Laravel Documentation, you create this database table structure:

teams
id - integer
name - string
 
players
id - integer
name - string
 
team_player
player_id - integer
team_id - integer

You create the corresponding Player model with a BelongsToMany relationship to the Team model

class Player extends Model
{
protected $fillable = ['name'];
 
public function teams()
{
return $this->belongsToMany(Team::class, 'team_player');
}
}
 
class Team extends Model
{
protected $fillable = ['name'];
 
public function players()
{
return $this->belongsToMany(Player::class, 'team_player');
}
}

You then attach Players to a Team by calling $team->attach([$player->id]), all is good!

Problem

Now in Filament, in your TeamResource, you add a Repeater field to let your site administrators attach Players to Teams:

public static function form(Form $form): Form
{
return $form
->schema([
TextInput::make('name'),
 
Repeater::make('players')
->relationship()
->schema([
Select::make('player_id')
->label('Player')
->options(Player::pluck('name', 'id')),
]),
]);
}

When you try to add a Player to a Team using the Repeater field and save, you see this error:

SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: players.name
 
INSERT INTO "players" ("updated_at", "created_at") VALUES (2023-07-17 13:55:12, 2023-07-17 13:55:12)

What's happening? Filament is trying to create a new Player record instead of simply attaching the selected Player to the Team.

Solution

  1. Introduce a new TeamPlayer model:
class TeamPlayer extends Model
{
protected $table = 'team_player';
 
protected $fillable = ['team_id', 'player_id'];
}
  1. Update your team_player table schema:
Schema::create('team_player', function (Blueprint $table) {
$table->id();
$table->integer('team_id');
$table->integer('player_id');
$table->timestamps();
});
  1. Update your Team-Player relationship to HasMany instead of BelongsToMany:
class Team extends Model
{
protected $fillable = ['name'];
 
public function teamPlayers()
{
return $this->hasMany(TeamPlayer::class, 'team_id', 'id');
}
}
  1. Update your Repeater field name:
Repeater::make('teamPlayers')
->relationship()
->schema([
Select::make('player_id')
->label('Player')
->options(Player::pluck('name', 'id')),
]),

The Repeater should now correctly attach Players to Teams as intended:

Edit Team Page

No comments yet…