Laravel: Eloquent - Many to Many Relationship
Carlos Costa
Many-to-many relationships are slightly more complicated than hasOne and hasMany relationships. An example of such a relationship is a user with many roles, where the roles are also shared by other users.
In this note we demostrate how to create a many-to-many relationship between two models and how to create a pivot table to store the relationship.
Let’s define a relationship between Client
and Flight
models.
First, we need to create our files.
php artisan make:model Client -mf --pest
php artisan make:model Flight -mf --pest
php artisan make:model Tickets -mfp --pest
-p
flag indicate that we want to create a pivot table.
Migrations
We need to create the migrations for our models.
//client
Schema::create('clients', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('name');
$table->string('email');
$table->integer('age');
});
//flight
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('name');
$table->string('destination');
$table->dateTime('departure');
});
//tickets
Schema::create('ticket', function (Blueprint $table) {
$table->foreignId('flight_id');
$table->foreignId('client_id');
$table->primary(['flight_id', 'client_id']);
$table->timestamps();
$table->string('seat');
});
Models
To that relationship work we need to define 3 models.
//client
class Client extends Model
{
use HasFactory;
public function flights()
{
return $this->belongsToMany(
Flight::class,
'tickets', //table name
'flight_id', //foreign key in pivot table
'client_id' //foreign key in pivot table
)
->as('ticket') //alias for the pivot table
->withTimestamps() //timestamps are optional
->withPivot('seat') //pivot table column
->using(Ticket::class); //pivot table model
}
}
//flight
class Flight extends Model
{
use HasFactory;
public function clients()
{
return $this->belongsToMany(
Clients::class,
'tickets', //table name
'flight_id', //foreign key in pivot table
'client_id' //foreign key in pivot table
)
->as('ticket') //alias for the pivot table
->withTimestamps() //timestamps are optional
->withPivot('seat') //pivot table column
->using(Ticket::class); //pivot table model
}
}
//ticket
class Tickets extends Pivot
{
//
}
Factories
We need to create factories for our models.
//client
class ClientFactory extends Factory
{
protected $model = Client::class;
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'age' => $this->faker->numberBetween(18, 80),
];
}
}
//flight
class FlightFactory extends Factory
{
protected $model = Flight::class;
public function definition()
{
return [
'flight_number' => $this->faker->unique()->randomNumber(4),
'departure_airport' => $this->faker->randomElement(['LAX', 'SFO', 'JFK', 'DEN', 'ORD']),
'arrival_airport' => $this->faker->randomElement(['LAX', 'SFO', 'JFK', 'DEN', 'ORD']),
'departure_time' => $this->faker->dateTimeBetween('now', '+1 year'),
];
}
}
Tests
To finally let’s apply some tests to our models.
public function test_models(): void
{
//create a client
$client = Client::factory()->create();
//create a flight
$flight = Flight::factory()->create();
//attach the client to the flight
$flight->clients()->attach($client->id, ['seat' => 'A1']);
//check if the client is attached to the flight
$ticket = Ticket::where('client_id', $client->id)->first();
$this->assertEquals($ticket->seat, 'A1');
}