Untuk praktikum ini, kita akan membuat sistem akademik sederhana dengan relationship:
Untuk detailnya, berikut adalah ERD untuk database yang akan diimplementasikan pada project kali ini:
Buat project baru dengan nama laravel-relationship, selanjutnya buat migrasi database.
Buat migrasi database dengan menggunakan command php artisan make:migration create_majors_table. Kemudian tambahkan kode berikut di function up()
Schema::create('majors', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
Buat migrasi database dengan menggunakan command php artisan make:migration create_students_table. Kemudian tambahkan kode berikut di 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();
});
Buat migrasi database dengan menggunakan command php artisan make:migration create_subjects_table. Kemudian tambahkan kode berikut di function up()
Schema::create('subjects', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('sks');
$table->timestamps();
});
Buat migrasi database dengan menggunakan command php artisan make:migration create_student_subject_table. Kemuidian tambahkan kode berikut di 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']);
});
Setelah semua migrasi telah dibuat dan disave, jalankan migrasi database dengan perintah php artisan migrate
Langkah selanjutnya adalah membuat model dengan relationship sesuai dengan migrasi database yang telah dibuat.
Buat model major dengan perintah php artisan make:model Major pada terminal. Setelah itu, masukkan kode berikut di model:
<?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 Student
public function students()
{
return $this->hasMany(Student::class);
}
}
Buat model student dengan perintah php artisan make:model Student pada terminal. Setelah itu, masukkan kode berikut di model:
<?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);
}
}
Buat model subject dengan perintah php artisan make:model Subject pada terminal. Setelah itu, masukkan kode berikut di model:
<?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);
}
}
Langkah selanjutnya adalah membuat seeder untuk memasukkan data ke database
Buat seeder dengan command php artisan make:seeder MajorSeeder, lalu isi seeder dengan kode berikut:
<?php
namespace Database\Seeders;
use App\Models\Major;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class MajorSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run()
{
$majors = [
['name' => 'Teknik Informatika'],
['name' => 'Sistem Informasi'],
['name' => 'Teknik Komputer'],
['name' => 'Manajemen Informatika'],
];
foreach ($majors as $major) {
Major::create($major);
}
}
}
Buat seeder dengan command php artisan make:seeder SubjectSeeder, lalu isi seeder dengan kode berikut:
<?php
namespace Database\Seeders;
use App\Models\Subject;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SubjectSeeder extends Seeder
{
/**
* Run the database seeds.
*/
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);
}
}
}
Buat seeder dengan command php artisan make:seeder StudentSeeder, lalu isi seeder dengan kode berikut:
<?php
namespace Database\Seeders;
use App\Models\Student;
use App\Models\Subject;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class StudentSeeder extends Seeder
{
/**
* Run the database seeds.
*/
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' => 'Dewi 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);
}
}
}
Tambahkan kode berikut pada DatabaseSeeder
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call([
MajorSeeder::class,
SubjectSeeder::class,
StudentSeeder::class,
]);
}
}
setelah semua seeder dibuat dan di save, jalankan dengan perintah php artisan db:seed
Langkah selanjutnya adlaah membuat controller pada model. Buat controller dengan perintah php artisan make:controller StudentController pada terminal. Kemudian tambahkan kode berikut di file controller:
<?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', 'mostFrequentMajor'));
}
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');
}
}
Langkah selanjutnya yaitu membuat Route untuk menentukan bagaimana aplikasi merespons permintaan (request) pada URL tertentu. Ubah kode pada web.php menjadi kode berikut:
<?php
use App\Http\Controllers\StudentController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return redirect()->route('students.index');
});
Route::resource('students', StudentController::class);
Langkah selanjutnya adalah membuat views untuk menampilkan pada halaman website
<!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>
@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
@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
jika sudah selesai maka tampilannya akan seperti berikut:
Index
Create
pertama, ubah dulu bagian index() pada StudentController dengan kode berikut:
// Eager loading untuk menghindari N+1 problem
$students = Student::with(['major', 'subjects'])->get();
// Cari jurusan dengan jumlah mahasiswa terbanyak
$mostFrequentMajor = Major::withCount('students')
->orderByDesc('students_count')
->first();
// Kirim ke view
return view('students.index', compact('students', 'mostFrequentMajor'));
Query ini berfungsi untuk mencari dan mengambil satu data jurusan (Major) yang memiliki jumlah mahasiswa terbanyak. Langkah pertamanya (Major::withCount(‘students’)) adalah mengambil semua data jurusan sambil menghitung jumlah mahasiswa yang berelasi dengan setiap jurusan, hasil hitungan ini disimpan dalam kolom virtual bernama students_count; kemudian (orderByDesc(‘students_count’)) hasilnya diurutkan secara menurun berdasarkan jumlah mahasiswa tersebut; dan terakhir (first()) diambil satu record pertama dari hasil pengurutan, yang secara efektif adalah jurusan dengan jumlah mahasiswa terbanyak.
Kemudian, pada index.blade.php, tambahkan kode berikut dibawah kode tabel:
@if ($mostFrequentMajor)
<div class="alert alert-info mt-4" role="alert">
Jurusan dengan mahasiswa terbanyak adalah: <strong>{{ $mostFrequentMajor->name }}</strong> (dengan {{ $mostFrequentMajor->students_count }} mahasiswa).
</div>
@endif
Hasilnya akan menjadi seperti berikut:
Kode view show.blade.php:
@extends('layouts.app')
@section('content')
<h2>Detail Mahasiswa</h2>
<div class="card">
<div class="card-body">
<div class="mb-3">
<label for="nim" class="form-label">NIM:</label>
<p id="nim">{{ $student->nim }}</p>
</div>
<div class="mb-3">
<label for="name" class="form-label">Nama:</label>
<p id="name">{{ $student->name }}</p>
</div>
<div class="mb-3">
<label for="address" class="form-label">Alamat:</label>
<p id="address">{{ $student->address }}</p>
</div>
<div class="mb-3">
<label for="major" class="form-label">Jurusan:</label>
<p id="major">{{ $student->major->name }}</p>
</div>
<div class="mb-3">
<label class="form-label">Mata Kuliah Diambil:</label>
@if ($student->subjects->count() > 0)
<ul>
@foreach ($student->subjects as $subject)
<li>{{ $subject->name }} ({{ $subject->sks }} SKS)</li>
@endforeach
</ul>
<p><strong>Total SKS:</strong> {{ $student->subjects->sum('sks') }}</p>
@else
<p>Tidak ada mata kuliah yang diambil.</p>
@endif
</div>
<a href="{{ route('students.index') }}" class="btn btn-secondary">Kembali ke Daftar</a>
</div>
</div>
@endsection
Hasilnya akan menjadi seperti berikut:
Kode view edit.blade.php:
@extends('layouts.app')
@section('content')
<h2>Edit Mahasiswa</h2>
<div class="card">
<div class="card-body">
<form action="{{ route('students.update', $student->id) }}" method="POST">
@csrf
@method('PUT') {{-- Use PUT method for updates --}}
<div class="mb-3">
<label for="nim" class="form-label">NIM</label>
{{-- Populate with existing student data, or old input on validation error --}}
<input type="text" class="form-control @error('nim') is-invalid @enderror"
id="nim" name="nim" value="{{ old('nim', $student->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', $student->name) }}">
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="address" class="form-label">Alamat</label>
<input type="text" class="form-control @error('address') is-invalid @enderror"
id="address" name="address" value="{{ old('address', $student->address) }}">
@error('address')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="major_id" class="form-label">Jurusan</label>
<select name="major_id" id="major_id" class="form-control @error('major_id') is-invalid @enderror" >
<option value="">Pilih Jurusan</option>
@foreach ($majors as $major)
{{-- Pre-select the current major --}}
<option value="{{ $major->id }}" {{ old('major_id', $student->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 type="checkbox" class="form-check-input" name="subjects[]"
value="{{ $subject->id }}" id="subject{{ $subject->id }}"
{{-- Check if the subject is currently associated with the student --}}
{{ in_array($subject->id, old('subjects', $student->subjects->pluck('id')->toArray())) ? 'checked' : '' }}>
<label for="subject{{ $subject->id }}" class="form-check-label">
{{ $subject->name }} ({{ $subject->sks }} SKS)
</label>
</div>
@endforeach
</div>
<button type="submit" class="btn btn-primary">Update</button>
<a href="{{ route('students.index') }}" class="btn btn-secondary">Batal</a>
</form>
</div>
</div>
@endsection
Hasilnya akan menjadi seperti berikut:
Belum ada komentar.