Many-to-Many — Laravel

Relasi many-to-many membutuhkan tabel pivot penghubung. Contoh: User bisa punya banyak Role, Role bisa punya banyak User Migration (tabel pivot)…

Relasi many-to-many menghubungkan dua tabel yang sama-sama bisa punya banyak pasangan di seberangnya. Di app Perpustakaan, satu orang bisa memegang beberapa peran (role), misalnya admin dan editor katalog, dan satu peran dipegang beberapa orang. Bayangin buku daftar tugas di meja petugas: tiap baris cuma berisi satu pasangan, "Budi pegang admin", "Budi pegang editor", "Ani pegang pustakawan". Mau tahu peran Budi, cari baris bernama Budi. Mau tahu siapa saja pustakawan, cari baris pustakawan. Buku daftar itu di database disebut tabel pivot.

🧪 Tebakan cepat: Budi memegang role 1 (admin) dan 2 (editor). Lalu $budi->roles()->sync([1, 3]); dijalankan. Sesudahnya Budi memegang role apa saja, dan baris mana di tabel pivot yang dihapus? Tebak dulu, jawabannya ada di bagian "attach, detach, sync".

Kenapa butuh tabel ketiga

Di pelajaran Relasi Eloquent, satu peminjaman cukup menyimpan user_id. Di sini itu gak cukup: kolom role_id di users cuma muat satu role, dan kolom user_id di roles cuma muat satu orang. Jadi pasangannya disimpan di tabel ketiga:

users roles role_user (pivot)
1 Budi 1 admin user 1, role 1
2 Ani 2 editor user 1, role 2
3 Citra 3 pustakawan user 2, role 3

Budi admin dan editor, Ani pustakawan, Citra anggota biasa tanpa role. Migration tabel pivot-nya:

Schema::create('role_user', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->foreignId('role_id')->constrained()->cascadeOnDelete();
    $table->primary(['user_id', 'role_id']);
});

belongsToMany di kedua model

use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class User extends Model
{
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }
}

class Role extends Model
{
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }
}
select "roles".*, "role_user"."user_id" as "pivot_user_id", "role_user"."role_id" as "pivot_role_id" from "roles" inner join "role_user" on "roles"."id" = "role_user"."role_id" where "role_user"."user_id" = 1

Hasilnya Collection berisi admin dan editor. Dari sisi seberang, Role::find(3)->users berisi Ani. $budi->roles->contains(1) menjawab true.

attach, detach, sync

Mengubah pasangan dilakukan lewat relasi dengan kurung, $budi->roles(). Tiap baris tabel ini mulai dari keadaan yang sama, Budi memegang role 1 dan 2. SQL-nya persis yang dikirim Laravel 13 ke SQLite:

Kode SQL yang dikirim Role Budi sesudahnya
attach(3) insert into "role_user" ("role_id", "user_id") values (3, 1) 1, 2, 3
detach(2) delete from "role_user" where "role_user"."user_id" = 1 and "role_user"."role_id" in (2) 1
sync([1, 3]) select * from "role_user" where "role_user"."user_id" = 1, lalu delete ... in (2), lalu insert ... values (3, 1) 1, 3
detach() delete from "role_user" where "role_user"."user_id" = 1 kosong

📌 Kolom tambahan di pivot

Pivot boleh punya kolom sendiri, misalnya kapan role-nya diberikan. Tambahkan $table->timestamps() di migration, lalu ->withTimestamps() di relasinya: return $this->belongsToMany(Role::class)->withTimestamps();. Kolom lain dibaca dengan ->withPivot('assigned_by'), diisi dengan attach(3, ['assigned_by' => 2]), dan dibaca lewat $role->pivot->assigned_by. Kalau satu tabel perlu dipasangkan ke banyak jenis model (tag untuk buku dan untuk artikel), lihat pelajaran Polymorphic Relations. Cara memakai role untuk izin dibahas di pelajaran Role-based Access Control.

Di latihan

Kode awal sudah punya class User dengan method roles() dan class Role dengan method users(), dua-duanya kosong. Isi roles() dengan return $this->belongsToMany(Role::class); dan users() dengan return $this->belongsToMany(User::class);. Simulator memuat kedua class lalu mencetak Class defined: User dan Class defined: Role. Relasinya dinilai dari kodenya: harus ada belongsToMany(.

⚠️ Jebakan yang sering ketemu

TL;DR: Many-to-many butuh tabel pivot, namanya dua model tunggal urut abjad: role_user, berisi user_id dan role_id dengan primary key gabungan. Di kedua model tulis return $this->belongsToMany(...): roles() di User, users() di Role. Baca tanpa kurung ($budi->roles, satu query dengan JOIN ke pivot), ubah dengan kurung: attach(3) menambah baris, detach(2) menghapus, detach() menghapus semua, dan sync([1, 3]) membuat isinya persis daftar itu (menghapus yang gak ada, menambah yang baru) lalu mengembalikan attached, detached, updated. attach pasangan yang sudah ada melanggar UNIQUE. Kolom tambahan di pivot memakai withTimestamps() dan withPivot(...).

Yang akan kamu pelajari