Panduan Tutorial Laravel

Belajar Laravel Batch 6: Relasi Database Dasar - Menghubungkan Data Aplikasi

Belajar Laravel Batch 6: Relasi Database Dasar - Menghubungkan Data Aplikasi
Twitter / X WhatsApp Facebook LinkedIn Telegram
Apa yang kamu butuhkan

Di Batch 6 ini, PahamITian akan menyelami dunia relasi database di Laravel. Kita akan belajar bagaimana menghubungkan tabel-tabel data, seperti artikel dengan kategori atau user, menggunakan konsep One-to-Many, metode Eloquent `belongsTo` dan `hasMany`, serta mengoptimalkan query dengan eager loading. Siap membuat aplikasi Laravel-mu lebih terstruktur dan powerful?

Belajar Laravel Batch 6: Relasi Database Dasar - Menghubungkan Data Aplikasi

Halo, PahamITian! Selamat datang kembali di petualangan Laravel kita! Setelah di Batch 5 kita berhasil membuat CRUD yang lengkap, sekarang saatnya kita melangkah lebih jauh dan membuat aplikasi kita lebih cerdas dengan menghubungkan data-data yang berbeda.

Bayangkan, artikel yang kita buat di Batch sebelumnya itu 'sendirian'. Padahal, biasanya artikel punya penulis (user) dan juga masuk ke dalam kategori tertentu. Nah, di Batch 6 ini, kita akan belajar bagaimana 'menjodohkan' data-data ini menggunakan Relasi Database Dasar di Laravel.

Relasi database adalah tulang punggung aplikasi web yang kompleks. Tanpa relasi, data kita akan tercerai-berai dan sulit dikelola. Laravel, dengan Eloquent ORM-nya, membuat pengelolaan relasi ini jadi super mudah dan intuitif. Yuk, kita mulai!

1. Apa Itu Relasi Database?

Secara sederhana, relasi database adalah cara kita mendefinisikan hubungan antara dua atau lebih tabel dalam database. Misalnya, satu user bisa menulis banyak artikel, atau satu kategori bisa memiliki banyak artikel. Ini membantu kita menghindari duplikasi data dan menjaga konsistensi.

Analogi: Bayangkan kamu punya dua daftar: daftar buku dan daftar penulis. Tanpa relasi, kamu mungkin akan menulis nama penulis berulang-ulang di setiap entri buku. Dengan relasi, kamu cukup menulis ID penulis di daftar buku, dan ID itu akan 'menunjuk' ke detail penulis di daftar penulis. Lebih rapi, kan?

2. Relasi One-to-Many: Satu Punya Banyak

Relasi yang paling umum dan akan kita pelajari pertama adalah One-to-Many (Satu-ke-Banyak). Ini berarti satu entitas (misalnya, satu User) bisa memiliki banyak entitas lain (misalnya, banyak Post). Atau, satu Category bisa memiliki banyak Post.

Contoh Kasus:
* User punya banyak Post: Satu user (penulis) bisa membuat banyak artikel. Tapi satu artikel hanya ditulis oleh satu user.
* Category punya banyak Post: Satu kategori (misalnya, 'Teknologi') bisa memiliki banyak artikel. Tapi satu artikel hanya masuk ke dalam satu kategori.

Untuk membuat relasi One-to-Many, kita biasanya menambahkan sebuah foreign key di tabel 'many' (tabel yang punya banyak data). Foreign key ini akan menyimpan ID dari tabel 'one' (tabel yang punya satu data).

Misalnya, untuk User punya banyak Post:
* Tabel users akan punya kolom id (primary key).
* Tabel posts akan punya kolom user_id (foreign key) yang merujuk ke id di tabel users.

3. Mempersiapkan Database (Migration)

Sebelum kita masuk ke Eloquent, kita perlu menambahkan kolom foreign key di tabel posts kita. Kita akan menambahkan category_id dan user_id.

Menambahkan category_id ke tabel posts

Pertama, kita buat migration baru untuk menambahkan kolom category_id ke tabel posts.

BASH
php artisan make:migration add_category_id_to_posts_table

Buka file migration yang baru dibuat di database/migrations/ (nama file akan mirip 2023_xx_xx_xxxxxx_add_category_id_to_posts_table.php). Edit metode up() dan down() seperti ini:

PHP
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            // Menambahkan kolom category_id
            // unsignedBigInteger karena id di tabel categories biasanya unsigned big integer
            $table->unsignedBigInteger('category_id')->nullable()->after('id');

            // Menambahkan foreign key constraint
            // Ini memastikan bahwa category_id yang dimasukkan harus ada di tabel categories
            // onDelete('cascade') berarti jika kategori dihapus, semua post yang terkait juga akan dihapus
            $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            // Menghapus foreign key sebelum menghapus kolom
            $table->dropForeign(['category_id']);
            // Menghapus kolom category_id
            $table->dropColumn('category_id');
        });
    }
};

Catatan: Kita menggunakan nullable() untuk sementara agar tidak error jika ada post yang sudah ada di database. Nanti kita bisa hapus nullable() jika semua post sudah punya kategori.

Menambahkan user_id ke tabel posts

Kita juga bisa menambahkan user_id dengan cara yang sama. Buat migration baru:

BASH
php artisan make:migration add_user_id_to_posts_table

Edit file migration:

PHP
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->foreignId('user_id')->nullable()->constrained()->onDelete('cascade');
            // 'foreignId()' adalah shorthand untuk unsignedBigInteger dan 'constrained()' otomatis membuat foreign key ke tabel 'users'
            // Jika nama tabel bukan 'users', bisa pakai constrained('nama_tabel')
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropForeign(['user_id']);
            $table->dropColumn('user_id');
        });
    }
};

Setelah itu, jalankan migration:

BASH
php artisan migrate

Sekarang tabel posts kita sudah punya kolom category_id dan user_id!

4. Model Eloquent: belongsTo dan hasMany

Sekarang kita akan mendefinisikan relasi ini di Model Eloquent kita. Laravel menyediakan dua metode utama untuk relasi One-to-Many:

  • belongsTo(): Digunakan di model 'many' (misalnya Post) untuk menunjukkan bahwa ia 'milik' satu entitas lain (misalnya Category atau User).
  • hasMany(): Digunakan di model 'one' (misalnya Category atau User) untuk menunjukkan bahwa ia 'memiliki banyak' entitas lain (misalnya Post).

Membuat Model Category

Kita butuh model dan migration untuk Category dulu. Jika belum ada, buatlah:

BASH
php artisan make:model Category -m

Edit migration create_categories_table (nama file mirip 2023_xx_xx_xxxxxx_create_categories_table.php):

PHP
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique(); // Nama kategori harus unik
            $table->string('slug')->unique(); // Slug untuk URL yang SEO friendly
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('categories');
    }
};

Jalankan migration:

BASH
php artisan migrate

Edit model app/Models/Category.php:

PHP
<?php

namespace App\Models;

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

class Category extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'slug'];

    // Relasi One-to-Many: Satu kategori punya banyak post
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

Mendefinisikan Relasi di Model Post

Sekarang, buka app/Models/Post.php dan tambahkan relasi belongsTo:

PHP
<?php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'slug',
        'excerpt',
        'content',
        'category_id', // Tambahkan ini
        'user_id',     // Tambahkan ini
    ];

    // Relasi One-to-Many: Satu post milik satu kategori
    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    // Relasi One-to-Many: Satu post milik satu user (penulis)
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Mendefinisikan Relasi di Model User

Terakhir, buka app/Models/User.php dan tambahkan relasi hasMany:

PHP
<?php

namespace App\Models;

// ... namespace lain

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    // ... fillable, hidden, casts

    // Relasi One-to-Many: Satu user punya banyak post
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

5. Menggunakan Relasi di Controller dan View

Setelah relasi didefinisikan di model, kita bisa dengan mudah mengakses data relasi tersebut.

Mengakses Data Relasi

Misalnya, di PostController kita ingin menampilkan daftar artikel beserta nama kategorinya:

PHP
// app/Http/Controllers/PostController.php

// ... use App\Models\Post; dan lainnya

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::all(); // Mengambil semua post
        return view('posts.index', compact('posts'));
    }

    public function show(Post $post)
    {
        // $post sudah otomatis diambil oleh Route Model Binding
        return view('posts.show', compact('post'));
    }
}

Di view resources/views/posts/index.blade.php:

BLADE
@foreach ($posts as $post)
    <div>
        <h2>{{ $post->title }}</h2>
        <p>Kategori: {{ $post->category->name }}</p> {{-- Mengakses nama kategori --}}
        <p>Penulis: {{ $post->user->name }}</p> {{-- Mengakses nama user --}}
        <p>{{ $post->excerpt }}</p>
        <a href="{{ route('posts.show', $post) }}">Baca Selengkapnya</a>
    </div>
    <hr>
@endforeach

Penting: Saat mengakses $post->category->name, Laravel secara otomatis akan menjalankan query terpisah untuk mengambil data kategori yang terkait dengan post tersebut. Ini disebut Lazy Loading.

6. Eager Loading (with()): Mengoptimalkan Query

Lazy loading memang praktis, tapi bisa menyebabkan masalah N+1 Query Problem. Bayangkan jika kita punya 100 artikel. Laravel akan menjalankan 1 query untuk mengambil 100 artikel, lalu 100 query terpisah lagi untuk mengambil kategori masing-masing artikel. Total 101 query! Ini tidak efisien dan bisa memperlambat aplikasi.

Solusinya adalah Eager Loading menggunakan metode with(). Dengan eager loading, kita memberitahu Laravel untuk mengambil data relasi bersamaan dengan data utama dalam satu atau dua query saja.

PHP
// app/Http/Controllers/PostController.php

// ... use App\Models\Post; dan lainnya

class PostController extends Controller
{
    public function index()
    {
        // Eager load category dan user
        $posts = Post::with(['category', 'user'])->get();
        return view('posts.index', compact('posts'));
    }

    public function show(Post $post)
    {
        // Jika ingin eager load di halaman detail juga
        $post->load(['category', 'user']); // Menggunakan load() jika objek sudah ada
        return view('posts.show', compact('post'));
    }
}

Dengan Post::with(['category', 'user'])->get();, Laravel akan menjalankan:
1. Satu query untuk mengambil semua posts.
2. Satu query untuk mengambil semua categories yang ID-nya ada di posts yang sudah diambil.
3. Satu query untuk mengambil semua users yang ID-nya ada di posts yang sudah diambil.

Total hanya 3 query, jauh lebih efisien daripada 101 query!

Latihan Praktik Batch 6

Yuk, kita terapkan apa yang sudah kita pelajari!

  1. Buat Model dan Migration Category: (Jika belum) Pastikan sudah ada tabel categories dengan kolom id, name, slug, timestamps.
  2. Tambahkan category_id dan user_id ke tabel posts: Gunakan migration seperti contoh di atas.
  3. Definisikan Relasi di Model:
  4. Buat Beberapa Data Kategori: Kamu bisa menggunakan Tinker atau menambahkan di database/seeders/CategorySeeder.php.
  5. Modifikasi Form Tambah/Edit Artikel: Tambahkan dropdown untuk memilih kategori saat membuat atau mengedit artikel. Kamu perlu mengambil daftar kategori dari database dan menampilkannya di form.
  6. Simpan category_id dan user_id: Saat menyimpan artikel baru atau mengupdate artikel, pastikan category_id dari form dan user_id dari user yang sedang login (sementara bisa hardcode dulu, nanti di Batch 7 kita bahas auth) ikut tersimpan.
  7. Tampilkan Kategori dan Penulis di Daftar Artikel: Modifikasi resources/views/posts/index.blade.php dan show.blade.php untuk menampilkan nama kategori dan penulis menggunakan relasi yang sudah dibuat ($post->category->name dan $post->user->name).
  8. Gunakan Eager Loading: Pastikan kamu menggunakan with(['category', 'user']) di PostController@index dan PostController@show untuk mengoptimalkan query.

Ringkasan Singkat Batch 6

Di Batch 6 ini, kita sudah berhasil memahami dan mengimplementasikan relasi database One-to-Many di Laravel. Kita belajar bagaimana:
* Menambahkan foreign key di tabel posts melalui migration.
* Mendefinisikan relasi belongsTo() di model Post.
* Mendefinisikan relasi hasMany() di model Category dan User.
* Mengakses data relasi dengan mudah di controller dan view.
* Mengoptimalkan query dengan Eager Loading (with()) untuk menghindari N+1 Query Problem.

Pertanyaan Cek Pemahaman

  1. Apa perbedaan antara belongsTo() dan hasMany() dalam konteks relasi One-to-Many?
  2. Mengapa kita perlu menambahkan foreign key di tabel posts untuk relasi Post dengan Category?
  3. Jelaskan apa itu N+1 Query Problem dan bagaimana eager loading (with()) menyelesaikannya!
  4. Jika kamu punya model Comment dan Post, di mana Comment belongsTo Post, bagaimana kamu akan mendefinisikan relasi ini di kedua model?

Kesalahan Umum Pemula

  • Lupa Menjalankan Migration: Setelah membuat atau memodifikasi migration, selalu ingat php artisan migrate!
  • Salah Penamaan Metode Relasi: Pastikan nama metode relasi di model sesuai dengan konvensi Laravel atau definisikan secara eksplisit nama foreign key-nya jika berbeda.
  • Tidak Menggunakan Eager Loading: Awalnya mungkin tidak terasa, tapi di aplikasi yang lebih besar, ini bisa jadi penyebab performa lambat.
  • Lupa Menambahkan foreignId atau unsignedBigInteger: Pastikan tipe data dan atribut (misalnya nullable(), constrained()) sesuai di migration.
  • Tidak Menambahkan fillable untuk foreign key: Jika kamu menggunakan mass assignment, category_id dan user_id harus ada di properti $fillable di model Post.

Persiapan untuk Batch Berikutnya

Selamat, PahamITian! Kamu sudah menguasai dasar-dasar relasi database di Laravel. Ini adalah skill yang sangat penting untuk membangun aplikasi yang lebih kompleks dan terstruktur. Di Batch 7, kita akan melangkah ke topik yang tak kalah penting: Authentication Dasar. Kita akan belajar bagaimana membuat sistem login/register sederhana, melindungi halaman, dan menghubungkan artikel dengan user yang sedang login. Siapkan dirimu!

Jangan ragu untuk bereksperimen dengan relasi yang sudah kamu buat. Coba tampilkan daftar artikel berdasarkan kategori, atau daftar artikel yang ditulis oleh user tertentu. Semakin banyak kamu mencoba, semakin paham kamu!