Implementasi Relationship di Laravel: One-to-Many dan Many-to-Many

Konsep Relationship dalam Laravel

One-to-Many

Relasi dimana satu model memiliki banyak model lain. Contoh: Satu jurusan memiliki banyak mahasiswa

Many-to-Many

Relasi dimana banyak model terhubung dengan banyak model lain. Contoh: Mahasiswa mengambil banyak mata kuliah

Eloquent ORM

Sistem ORM di Laravel yang memudahkan interaksi dengan database termasuk relationship

Pivot Table

Tabel perantara untuk menghubungkan relasi many-to-many, menyimpan foreign key dari kedua model

Langkah Implementasi sesuai Modul

A
Persiapan Awal
Struktur Database dan Relationship:
  • Student belongs to Major (Many-to-One)
  • Student belongs to many Subject (Many-to-Many)
  • Major has many Student (One-to-Many)
  • Subject belongs to many Student (Many-to-Many)
ERD (Entity Relationship Diagram):
MAJOR
Type Column Constraint
bigint id PK
string name
timestamp created_at
timestamp updated_at

has many   |   belongs to

STUDENT
Type Column Constraint
bigint id PK
string nim UK
string name
text address
bigint major_id FK
timestamp created_at
timestamp updated_at
B
Membuat Migration
A. Migration untuk tabel majors
php artisan make:migration create_majors_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('majors', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('majors');
    }
};
B. Migration untuk tabel students
php artisan make:migration create_students_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('students', function (Blueprint $table) {
            $table->id();
            $table->string('nim')->unique();
            $table->string('name');
            $table->text('address');
            $table->foreignId('major_id')->constrained('majors')->onDelete('cascade');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('students');
    }
};
C. Migration untuk tabel subjects
php artisan make:migration create_subjects_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('subjects', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->integer('sks');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('subjects');
    }
};
D. Migration untuk tabel pivot student_subject
php artisan make:migration create_student_subject_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('student_subject', function (Blueprint $table) {
            $table->id();
            $table->foreignId('student_id')->constrained('students')->onDelete('cascade');
            $table->foreignId('subject_id')->constrained('subjects')->onDelete('cascade');
            $table->timestamps();
            // Mencegah duplikasi kombinasi student_id dan subject_id
            $table->unique(['student_id', 'subject_id']);
        });
    }
    
    public function down()
    {
        Schema::dropIfExists('student_subject');
    }
};
Jalankan Migration
php artisan migrate
C
Membuat Model dengan Relationship
A. Model Major
php artisan make:model Major
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Major extends Model
{
    use HasFactory;

    protected $fillable = ['name'];

    // Relationship: One Major has many Students
    public function students()
    {
        return $this->hasMany(Student::class);
    }
}
B. Model Student
php artisan make:model Student
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    use HasFactory;

    protected $fillable = ['nim', 'name', 'address', 'major_id'];
    
    // Relationship: Many Students belong to one Major
    public function major()
    {
        return $this->belongsTo(Major::class);
    }

    // Relationship: Many Students belong to many Subjects
    public function subjects()
    {
        return $this->belongsToMany(Subject::class);
    }
}
C. Model Subject
php artisan make:model Subject
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Subject extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'sks'];
    
    // Relationship: Many Subjects belong to many Students
    public function students()
    {
        return $this->belongsToMany(Student::class);
    }
}
D
Seeder untuk Data Sample
A. Seeder untuk Major
php artisan make:seeder MajorSeeder
<?php
namespace Database\Seeders;

use App\Models\Major;
use Illuminate\Database\Seeder;

class MajorSeeder extends Seeder
{
    public function run()
    {
        $majors = [
            ['name' => 'Teknik Informatika'], 
            ['name' => 'Sistem Informasi'], 
            ['name' => 'Teknik Komputer'], 
            ['name' => 'Manajemen Informatika']
        ];
        
        foreach ($majors as $major) {
            Major::create($major);
        }
    }
}
B. Seeder untuk Subject
php artisan make:seeder SubjectSeeder
<?php
namespace Database\Seeders;

use App\Models\Subject;
use Illuminate\Database\Seeder;

class SubjectSeeder extends Seeder
{
    public function run()
    {
        $subjects = [
            ['name' => 'Pemrograman Web', 'sks' => 3],
            ['name' => 'Database', 'sks' => 3],
            ['name' => 'Algoritma', 'sks' => 2],
            ['name' => 'Jaringan Komputer', 'sks' => 3],
            ['name' => 'Sistem Operasi', 'sks' => 2]
        ];
        
        foreach ($subjects as $subject) {
            Subject::create($subject);
        }
    }
}
C. Seeder untuk Student
php artisan make:seeder StudentSeeder
<?php
namespace Database\Seeders;

use App\Models\Student;
use App\Models\Major;
use App\Models\Subject;
use Illuminate\Database\Seeder;

class StudentSeeder extends Seeder
{
    public function run()
    {
        $students = [
            ['nim' => '20210001', 'name' => 'Ahmad Rizki', 'address' => 'Jl. Merdeka No. 1', 'major_id' => 1],
            ['nim' => '20210002', 'name' => 'Siti Nurhaliza', 'address' => 'Jl. Sudirman No. 15', 'major_id' => 1],
            ['nim' => '20210003', 'name' => 'Budi Santoso', 'address' => 'Jl. Pahlawan No. 8', 'major_id' => 2],
            ['nim' => '20210004', 'name' => 'Devi Kartika', 'address' => 'Jl. Diponegoro No. 22', 'major_id' => 2],
            ['nim' => '20210005', 'name' => 'Eko Prasetyo', 'address' => 'Jl. Gatot Subroto No. 11', 'major_id' => 3],
        ];
        
        foreach ($students as $studentData) {
            $student = Student::create($studentData);
            // Assign random subjects to each student
            $subjects = Subject::inRandomOrder()->take(rand(2, 4))->pluck('id');
            $student->subjects()->attach($subjects);
        }
    }
}
D. Update DatabaseSeeder
<?php
namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call([
            MajorSeeder::class,
            SubjectSeeder::class,
            StudentSeeder::class,
        ]);
    }
}
Jalankan Seeder
php artisan db:seed
E
Membuat Controller
StudentController
php artisan make:controller StudentController
<?php
namespace App\Http\Controllers;

use App\Models\Student;
use App\Models\Major;
use App\Models\Subject;
use Illuminate\Http\Request;

class StudentController extends Controller
{
    public function index()
    {
        // Eager loading untuk menghindari N+1 problem
        $students = Student::with(['major', 'subjects'])->get();
        return view('students.index', compact('students'));
    }

    public function show($id)
    {
        $student = Student::with(['major', 'subjects'])->findOrFail($id);
        return view('students.show', compact('student'));
    }

    public function create()
    {
        $majors = Major::all();
        $subjects = Subject::all();
        return view('students.create', compact('majors', 'subjects'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'nim' => 'required|unique:students',
            'name' => 'required',
            'address' => 'required',
            'major_id' => 'required|exists:majors,id',
            'subjects' => 'required|array',
            'subjects.*' => 'exists:subjects,id',
        ]);
        
        $student = Student::create($request->only(['nim', 'name', 'address', 'major_id']));
        $student->subjects()->attach($request->subjects);

        return redirect()->route('students.index')->with('success', 'Student created successfully');
    }

    public function edit($id)
    {
        $student = Student::with('subjects')->findOrFail($id);
        $majors = Major::all();
        $subjects = Subject::all();
        return view('students.edit', compact('student', 'majors', 'subjects'));
    }

    public function update(Request $request, $id)
    {
        $student = Student::findOrFail($id);

        $request->validate([
            'nim' => 'required|unique:students,nim,' . $student->id,
            'name' => 'required',
            'address' => 'required',
            'major_id' => 'required|exists:majors,id',
            'subjects' => 'required|array',
            'subjects.*' => 'exists:subjects,id',
        ]);
        
        $student->update($request->only(['nim', 'name', 'address', 'major_id']));
        $student->subjects()->sync($request->subjects);

        return redirect()->route('students.index')->with('success', 'Student updated successfully');
    }

    public function destroy($id)
    {
        $student = Student::findOrFail($id);
        $student->subjects()->detach(); // Remove all subject relationships
        $student->delete();
        
        return redirect()->route('students.index')->with('success', 'Student deleted successfully');
    }
}
F
Membuat Routes
<?php
use App\Http\Controllers\StudentController;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return redirect()->route('students.index');
});

Route::resource('students', StudentController::class);
G
Membuat Views
A. Layout Utama (app.blade.php)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Student Management System</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container">
            <a class="navbar-brand" href="{{ route('students.index') }}">Student Management</a>
        </div>
    </nav>

    <div class="container mt-4">
        @if(session('success'))
        <div class="alert alert-success">
            {{ session('success') }}
        </div>
        @endif
        @yield('content')
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
B. Index Students (index.blade.php)
@extends('layouts.app')

@section('content')
<div class="d-flex justify-content-between align-items-center mb-4">
    <h2>Daftar Mahasiswa</h2>
    <a href="{{ route('students.create') }}" class="btn btn-primary">Tambah Mahasiswa</a>
</div>

<div class="table-responsive">
    <table class="table table-striped">
        <thead>
            <tr>
                <th>NIM</th>
                <th>Nama</th>
                <th>Jurusan</th>
                <th>Mata Kuliah</th>
                <th>Total SKS</th>
                <th>Aksi</th>
            </tr>
        </thead>
        <tbody>
            @foreach($students as $student)
            <tr>
                <td>{{ $student->nim }}</td>
                <td>{{ $student->name }}</td>
                <td>{{ $student->major->name }}</td>
                <td>
                    @foreach($student->subjects as $subject)
                    <span class="badge bg-secondary me-1">{{ $subject->name }}</span>
                    @endforeach
                </td>
                <td>{{ $student->subjects->sum('sks') }}</td>
                <td>
                    <a href="{{ route('students.show', $student->id) }}" class="btn btn-info btn-sm">Detail</a>
                    <a href="{{ route('students.edit', $student->id) }}" class="btn btn-warning btn-sm">Edit</a>
                    <form action="{{ route('students.destroy', $student->id) }}" method="POST" class="d-inline">
                        @csrf
                        @method('DELETE')
                        <button type="submit" class="btn btn-danger btn-sm"
                            onclick="return confirm('Yakin ingin menghapus')">Hapus</button>
                    </form>
                </td>
            </tr>
            @endforeach
        </tbody>
    </table>
</div>
@endsection
C. Create Student (create.blade.php)
@extends('layouts.app')

@section('content')
<h2>Tambah Mahasiswa</h2>

<div class="card">
    <div class="card-body">
        <form action="{{ route('students.store') }}" method="POST">
            @csrf

            <div class="mb-3">
                <label for="nim" class="form-label">NIM</label>
                <input type="text" class="form-control @error('nim') is-invalid @enderror"
                    id="nim" name="nim" value="{{ old('nim') }}">
                @error('nim')
                <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <div class="mb-3">
                <label for="name" class="form-label">Nama</label>
                <input type="text" class="form-control @error('name') is-invalid @enderror"
                    id="name" name="name" value="{{ old('name') }}">
                @error('name')
                <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <div class="mb-3">
                <label for="address" class="form-label">Alamat</label>
                <textarea class="form-control @error('address') is-invalid @enderror"
                    id="address" name="address" rows="3">{{ old('address') }}</textarea>
                @error('address')
                <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <div class="mb-3">
                <label for="major_id" class="form-label">Jurusan</label>
                <select class="form-control @error('major_id') is-invalid @enderror"
                    id="major_id" name="major_id">
                    <option value="">Pilih Jurusan</option>
                    @foreach($majors as $major)
                    <option value="{{ $major->id }}" {{ old('major_id') == $major->id ? 'selected' : '' }}>
                        {{ $major->name }}
                    </option>
                    @endforeach
                </select>
                @error('major_id')
                <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <div class="mb-3">
                <label class="form-label">Mata Kuliah</label>
                @error('subjects')
                <div class="text-danger">{{ $message }}</div>
                @enderror
                @foreach($subjects as $subject)
                <div class="form-check">
                    <input class="form-check-input" type="checkbox" name="subjects[]"
                        value="{{ $subject->id }}" id="subject{{ $subject->id }}"
                        {{ in_array($subject->id, old('subjects', [])) ? 'checked' : '' }}>
                    <label class="form-check-label" for="subject{{ $subject->id }}">
                        {{ $subject->name }} ({{ $subject->sks }} SKS)
                    </label>
                </div>
                @endforeach
            </div>
            
            <button type="submit" class="btn btn-primary">Simpan</button>
            <a href="{{ route('students.index') }}" class="btn btn-secondary">Kembali</a>
        </form>
    </div>
</div>
@endsection

Latihan dan Tugas

Latihan 1: Query dengan Relationship

Buat query untuk menampilkan:

  1. Semua mahasiswa beserta jurusan dan mata kuliahnya
  2. Jurusan yang memiliki mahasiswa terbanyak
  3. Mata kuliah yang diambil oleh mahasiswa tertentu
  4. Total SKS yang diambil setiap mahasiswa
Kesimpulan

Dalam modul ini, mahasiswa telah mempelajari:

  1. Cara membuat relationship One-to-Many dan Many-to-Many di Laravel
  2. Implementasi foreign key dan pivot table
  3. Penggunaan Eloquent relationship untuk query data
  4. Best practices dalam menggunakan eager loading
  5. Cara menampilkan data relationship di view

Relationship adalah konsep fundamental dalam pengembangan aplikasi web dengan database. Pemahaman yang baik tentang relationship akan membantu dalam membangun aplikasi yang efisien dan mudah di-maintain.

Komentar (0)

Tinggalkan Komentar