Pendahuluan — dari CRUD buku ke pinjam

Artikel ini adalah #62 (ini) di Seri 5: Laravel Lanjutan (di roadmap sering disebut Framework-based). Domain tetap perpustakaan mini.

Di CRUD API Buku: Ubah & Hapus (#61) kamu sudah melengkapi CRUD buku. Capstone (#60) memberi baca + login + tambah. Sekarang rak buku saja belum cukup: perpustakaan butuh anggota dan catatan peminjaman yang saling terhubung.

Awam: relasi = hubungan antar data. Satu anggota bisa punya banyak pinjaman. Satu pinjaman menunjuk satu anggota dan satu buku. Belum Capstone pinjam-kembali penuh — fokusnya “menghubungkan tabel” dulu.

Prasyarat: sudah baca CRUD ubah & hapus (#61) dan Capstone (#60). Pakai Laravel 11+.

Spesifikasi fitur — apa yang kita bangun?

Daftar singkat yang bisa dijelaskan ke teman:

  1. Anggota — orang yang boleh meminjam (nama + ID).
  2. Peminjaman — catatan: siapa meminjam buku mana, status aktif atau sudah kembali.
  3. Hubungan — dari anggota lihat daftar pinjamannya; dari pinjaman lihat anggota dan buku.

Awam: bayangkan kartu anggota di meja loket, dan slip pinjam yang menempel nomor anggota + nomor buku. Eloquent membantu membaca slip itu tanpa menulis “cari manual” berkali-kali.

Istilah — ringkas untuk relasi

Istilah Arti awam Catatan
Relasi Hubungan antar baris data (anggota ↔ pinjam ↔ buku) Bukan “teman di media sosial”
Kunci asing Nomor di baris pinjam yang menunjuk anggota/buku Di kode sering anggota_id, buku_id
hasMany “satu punya banyak” — anggota punya banyak pinjaman Nama fungsi Eloquent
belongsTo “banyak milik satu” — pinjaman milik satu anggota Kebalikan arah dari hasMany
Eloquent Cara Laravel membaca/menulis tabel lewat model Sudah muncul di Capstone & service

Urutan belajar: data terpisah dulu -> tautkan dengan nomor -> baru bungkus Eloquent.

Kenapa PHP biasa dulu?

Ide “slip pinjam menyimpan nomor anggota dan nomor buku” lebih mudah dirasakan di array PHP. Kalau alurnya klik, cuplikan hasMany / belongsTo terasa bungkus yang sama.

<?php
// Mini: gabungkan pinjaman dengan nama anggota & judul buku.
$anggota = [
    1 => ["nama" => "Siti"],
    2 => ["nama" => "Budi"],
];
$buku = [
    10 => ["judul" => "Dasar PHP"],
    11 => ["judul" => "Belajar Laravel"],
];
$pinjaman = [
    ["id" => 1, "anggota_id" => 1, "buku_id" => 10, "status" => "aktif"],
    ["id" => 2, "anggota_id" => 1, "buku_id" => 11, "status" => "kembali"],
];

$idAnggota = 1;
$daftar = [];
foreach ($pinjaman as $p) {
    if ($p["anggota_id"] !== $idAnggota) {
        continue;
    }
    $daftar[] = [
        "pinjam_id" => $p["id"],
        "anggota" => $anggota[$p["anggota_id"]]["nama"],
        "buku" => $buku[$p["buku_id"]]["judul"],
        "status" => $p["status"],
    ];
}

echo json_encode(["ok" => true, "pinjaman" => $daftar], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

Output (bentuknya mirip):

{
    "ok": true,
    "pinjaman": [
        {
            "pinjam_id": 1,
            "anggota": "Siti",
            "buku": "Dasar PHP",
            "status": "aktif"
        },
        {
            "pinjam_id": 2,
            "anggota": "Siti",
            "buku": "Belajar Laravel",
            "status": "kembali"
        }
    ]
}

Awam: anggota_id dan buku_id = nomor yang menempel di slip. Loop di atas = “cari semua slip milik Siti, lalu tulis nama + judul”. Eloquent nanti mengganti loop manual itu.

Relasi: Anggota -> Peminjaman -> Buku Anggota hasMany pinjaman Peminjaman anggota_id + buku_id Buku belongsTo dari pinjam Satu anggota boleh banyak pinjaman. Satu pinjaman menunjuk satu anggota dan satu buku. CRUD buku tetap (langkah sebelumnya); di sini kita menambah lapisan “siapa meminjam apa”.
Anggota dan buku sudah dikenal; #62 (ini) menambahkan slip peminjaman yang menghubungkan keduanya.

Alur baca pinjaman — PHP sederhana

Setelah data terhubung, kita bisa menjawab: “pinjaman aktif milik anggota berapa?”

<?php
$anggota = [1 => ["nama" => "Siti"]];
$buku = [10 => ["judul" => "Dasar PHP"]];
$pinjaman = [
    ["id" => 1, "anggota_id" => 1, "buku_id" => 10, "status" => "aktif"],
    ["id" => 2, "anggota_id" => 1, "buku_id" => 10, "status" => "kembali"],
];

function pinjamanAktifAnggota(int $anggotaId, array $pinjaman, array $anggota, array $buku): array
{
    if (! isset($anggota[$anggotaId])) {
        return ["status" => 404, "body" => ["pesan" => "Anggota tidak ketemu"]];
    }

    $hasil = [];
    foreach ($pinjaman as $p) {
        if ($p["anggota_id"] !== $anggotaId || $p["status"] !== "aktif") {
            continue;
        }
        if (! isset($buku[$p["buku_id"]])) {
            return ["status" => 404, "body" => ["pesan" => "Buku tidak ketemu di slip"]];
        }
        $hasil[] = [
            "pinjam_id" => $p["id"],
            "anggota" => $anggota[$anggotaId]["nama"],
            "buku" => $buku[$p["buku_id"]]["judul"],
            "status" => $p["status"],
        ];
    }

    return ["status" => 200, "body" => ["ok" => true, "pinjaman" => $hasil]];
}

$r = pinjamanAktifAnggota(1, $pinjaman, $anggota, $buku);
http_response_code($r["status"]);
echo json_encode($r["body"], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

Awam: filter status === "aktif" = hanya slip yang belum dikembalikan. Kalau anggota tidak ada = 404 — sama seperti “buku tidak ketemu” di #61.

Laravel — cuplikan relasi Eloquent (bukan file mandiri)

Di proyek Laravel, ide yang sama ditulis di model. Cuplikan di bawah bukan dijalankan dengan php file.php:

<?php
// Cuplikan Laravel (bukan file mandiri) — model Anggota.
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Anggota extends Model
{
    public function peminjaman(): HasMany
    {
        return $this->hasMany(Peminjaman::class);
    }
}

Awam: hasMany = “satu anggota punya banyak baris peminjaman”. HasMany = tipe kembalian — boleh diabaikan dulu.

<?php
// Cuplikan Laravel — model Peminjaman mengarah ke anggota & buku.
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Peminjaman extends Model
{
    public function anggota(): BelongsTo
    {
        return $this->belongsTo(Anggota::class);
    }

    public function buku(): BelongsTo
    {
        return $this->belongsTo(Buku::class);
    }
}

Awam: belongsTo = “baris ini milik satu anggota / satu buku”. Setelah itu, di controller/service kamu bisa menulis gaya: $anggota->peminjaman atau $pinjam->buku — Eloquent yang mengisi dari kunci asing.

Pola Dasar — anggota & peminjaman

  1. 1
    Rapikan CRUD buku dulu
    Pastikan alur di #61 sudah “klik”.
  2. 2
    Buat tabel anggota & peminjaman
    Migrasi (skrip buat/ubah tabel): kolom anggota_id dan buku_id di slip pinjam.
  3. 3
    Tulis hasMany / belongsTo
    Satu arah “punya banyak”, arah balik “milik satu”.
  4. 4
    Baca lewat relasi
    Dari anggota ambil daftar pinjam; dari pinjam ambil buku.
  5. 5
    Jaga ID kosong
    Anggota/buku tidak ketemu = 404, jangan pura-pura sukses.
  6. 6
    Uji dua arah
    Anggota tanpa pinjam · pinjam aktif · ID palsu.

Kode lengkap — demo mandiri relasi

Simpan sebagai laravel_eloquent_relasi_peminjaman_demo.php, lalu jalankan php laravel_eloquent_relasi_peminjaman_demo.php:

<?php
declare(strict_types=1);

$anggota = [
    1 => ["nama" => "Siti"],
    2 => ["nama" => "Budi"],
];
$buku = [
    10 => ["judul" => "Dasar PHP"],
    11 => ["judul" => "Belajar Laravel"],
];
$pinjaman = [
    ["id" => 1, "anggota_id" => 1, "buku_id" => 10, "status" => "aktif"],
    ["id" => 2, "anggota_id" => 1, "buku_id" => 11, "status" => "kembali"],
    ["id" => 3, "anggota_id" => 2, "buku_id" => 10, "status" => "aktif"],
];

function pinjamanAktifAnggota(int $anggotaId, array $pinjaman, array $anggota, array $buku): array
{
    if (! isset($anggota[$anggotaId])) {
        return ["status" => 404, "body" => ["pesan" => "Anggota tidak ketemu"]];
    }

    $hasil = [];
    foreach ($pinjaman as $p) {
        if ($p["anggota_id"] !== $anggotaId || $p["status"] !== "aktif") {
            continue;
        }
        if (! isset($buku[$p["buku_id"]])) {
            return ["status" => 404, "body" => ["pesan" => "Buku tidak ketemu di slip"]];
        }
        $hasil[] = [
            "pinjam_id" => $p["id"],
            "anggota" => $anggota[$anggotaId]["nama"],
            "buku" => $buku[$p["buku_id"]]["judul"],
            "status" => $p["status"],
        ];
    }

    return ["status" => 200, "body" => ["ok" => true, "pinjaman" => $hasil]];
}

function demo(string $judul, callable $aksi): void
{
    echo "=== {$judul} ===", PHP_EOL;
    $hasil = $aksi();
    echo "status: ", $hasil["status"], PHP_EOL;
    echo json_encode($hasil["body"], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL, PHP_EOL;
}

demo("Anggota palsu -> 404", function () use ($pinjaman, $anggota, $buku) {
    return pinjamanAktifAnggota(99, $pinjaman, $anggota, $buku);
});

demo("Siti pinjam aktif -> 200", function () use ($pinjaman, $anggota, $buku) {
    return pinjamanAktifAnggota(1, $pinjaman, $anggota, $buku);
});

demo("Budi pinjam aktif -> 200", function () use ($pinjaman, $anggota, $buku) {
    return pinjamanAktifAnggota(2, $pinjaman, $anggota, $buku);
});

Awam: demo(...) hanya membungkus output di terminal. callable = sesuatu yang bisa dipanggil seperti fungsi. declare(strict_types=1); membuat tipe lebih ketat — boleh diikuti, tidak wajib dihafal. Alur penting: ID palsu, Siti punya pinjam aktif, Budi punya pinjam aktif.

Kesalahan umum

Gejala Penyebab tipikal Perbaikan awam
Pinjaman kosong padahal data ada Salah anggota_id / lupa filter status Cetak ID di demo; cek “aktif” vs “kembali”
Bingung hasMany vs belongsTo Membalik arah “punya banyak” vs “milik satu” Anggota hasMany pinjam; pinjam belongsTo anggota
Judul buku kosong buku_id tidak ketemu di katalog Jawab 404 atau perbaiki kunci asing
Daftar pinjam terasa lambat saat data banyak Membaca buku/anggota satu-satu di dalam loop (sering disebut masalah N+1) Nanti dilatih saat daftar panjang (pagination) — cukup tahu dulu

Latihan singkat

  1. Ubah demo: tambah kasus “anggota tanpa pinjam aktif” dan pastikan daftar kosong tapi status tetap 200.
  2. Jelaskan ke teman: beda hasMany dan belongsTo dengan analogi kartu anggota + slip pinjam.
  3. Tulis satu kalimat: kenapa slip pinjam menyimpan nomor, bukan menyalin seluruh nama anggota.

FAQ singkat

Haruskah hafal semua jenis relasi Eloquent?
Belum. Untuk perpustakaan mini, hasMany dan belongsTo sudah cukup. Relasi “banyak-ke-banyak” bisa menyusul kalau dibutuhkan.

Kenapa belum Capstone pinjam-kembali?
Karena fondasi hubungan data harus jelas dulu. Alur pinjam + kembali penuh ada di akhir Seri 5.

Ke mana setelah ini?
Berikutnya alami: pagination, filter & pencarian — daftar pinjaman/buku yang panjang tetap nyaman dibaca. Belum perlu hardlink; tunggu artikel berikutnya LIVE.

Kesimpulan

Kamu sudah melangkah dari CRUD buku ke relasi: anggota dan peminjaman terhubung lewat nomor (anggota_id, buku_id). PHP array dulu; Eloquent hasMany / belongsTo adalah bungkus yang sama.

Seri 5 progress: langkah #62 (ini) · 2/8 Laravel Lanjutan · prasyarat: CRUD ubah & hapus (#61) LIVE. Berikutnya: Pagination, filter & pencarian — soft, belum hardlink.