WebTech Interaktif XI RPL

Laravel · Bab 5

Lab 2: Migrasi & Model Eloquent

Membangun tabel categories, products, dan transactions sebagai kode lewat migrasi, lengkap dengan foreign key, model Eloquent, relasi, dan data awal dari seeder.

Di lab ini kamu menerapkan Database as Code: struktur tiga tabel Smart Retail App (categories, products, transactions) ditulis sebagai file migrasi, lalu setiap tabel dipasangkan dengan Model Eloquent beserta relasinya. Contoh kode di halaman ini memakai studi kasus perpustakaan (authors dan books); terapkan polanya ke tabel Retail Store sesuai desain di Bab 3.

Alur Langkah

  1. Category — jalankan php artisan make:model untuk model Category beserta migrasinya (-m), isi method up() migrasi (id, name unik, description opsional, timestamps), lalu isi Model dengan $fillable dan relasi hasMany ke Product.
  2. Product — buat model + migrasi Product, isi skema kolom (category_id sebagai foreign key, product_code unik, name, price, stock dengan nilai awal 0, timestamps), lalu isi Model dengan $fillable, relasi belongsTo ke Category, dan hasMany ke Transaction.
  3. Transaction — buat model + migrasi Transaction, isi skema (product_id sebagai foreign key, invoice_number unik, quantity, total_price, transaction_date, timestamps), lalu isi Model dengan $fillable dan relasi belongsTo ke Product.
  4. Migrasi & seeder — jalankan php artisan migrate, isi database/seeders/DatabaseSeeder.php dengan tiga kategori awal dan satu akun kasir (password di-hash), lalu jalankan php artisan db:seed.

Model & Migrasi dengan Artisan

Satu perintah membuat Model sekaligus file migrasinya:

php artisan make:model <NamaModel> -m

Opsi -m meminta Artisan membuat file migrasi database/migrations/xxxx_xx_xx_xxxxxx_create_<nama_tabel>_table.php, sedangkan Model dibuat di app/Models/<NamaModel>.php. Nama tabel diturunkan otomatis dari nama Model sesuai aturan emas di Bab 2 (tunggal → jamak).

Schema Builder

Isi method up() pada file migrasi mendefinisikan kolom tabel. Contoh tabel authors:

public function up(): void
{
    Schema::create('authors', function (Blueprint $table) {
        $table->id();
        $table->string('name')->unique();
        $table->string('bio')->nullable();
        $table->integer('book_count')->default(0);
        $table->date('born_at')->nullable();
        $table->timestamps();
    });
}
PemanggilanArti
$table->id()Kolom id auto-increment sebagai primary key
string('kolom')Teks pendek (VARCHAR)
integer('kolom')Bilangan bulat, misalnya harga dan stok
date('kolom')Tanggal
->unique()Nilai tidak boleh kembar
->nullable()Boleh kosong
->default(0)Nilai awal bila tidak diisi
timestamps()Kolom created_at dan updated_at

Foreign Key Constraint

Tabel anak menyimpan ID baris tabel induk. Contoh: setiap buku menunjuk satu penulis.

$table->foreignId('author_id')->constrained('authors')->onDelete('cascade');
  • foreignId('author_id') membuat kolom penampung ID induk.
  • constrained('authors') memasang aturan di level database bahwa nilainya wajib ada di tabel authors.
  • onDelete('cascade') ikut menghapus baris anak ketika baris induknya dihapus.

Urutan migrasi penting. Migrasi dijalankan berdasarkan timestamp di awal nama file, jadi tabel induk harus dibuat lebih dulu daripada tabel anak. Di Retail Store urutannya categories → products → transactions, sesuai urutan kamu menjalankan make:model.

Model, $fillable & Relasi

Model mewakili satu tabel. Daftarkan kolom yang boleh diisi massal di $fillable, lalu definisikan relasinya sebagai method:

class Author extends Model
{
    use HasFactory;

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

    // Satu penulis memiliki BANYAK buku
    public function books()
    {
        return $this->hasMany(Book::class, 'author_id');
    }
}

Sisi sebaliknya ada di Model Book:

// Satu buku dimiliki SATU penulis
public function author()
{
    return $this->belongsTo(Author::class, 'author_id');
}

Di Retail Store, pola yang sama dipakai dua kali: Category hasMany Product (Product belongsTo Category), dan Product hasMany Transaction (Transaction belongsTo Product). Pastikan semua kolom yang diisi dari form tercantum di $fillable masing-masing Model.

Menjalankan Migrasi & Seeder

Jalankan migrasi untuk membuat semua tabel di database:

php artisan migrate

Jika perlu mengulang dari nol (misalnya setelah memperbaiki urutan migrasi), php artisan migrate:fresh menghapus semua tabel lalu menjalankan ulang seluruh migrasi secara berurutan.

Seeder mengisi data awal secara seragam. Isi method run() di database/seeders/DatabaseSeeder.php:

use App\Models\Author;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

public function run(): void
{
    Author::create(['name' => 'Andrea Hirata', 'bio' => 'Penulis Laskar Pelangi']);

    User::create([
        'name'     => 'Petugas Perpustakaan',
        'email'    => '<email-petugas>',
        'password' => Hash::make('<kata-sandi>'),
    ]);
}

Password wajib di-hash dengan Hash::make, karena proses login di Lab 7 membandingkan hash, bukan teks biasa. Lalu jalankan:

php artisan db:seed

Hasil yang Diharapkan

Di HeidiSQL/phpMyAdmin, database db_retail_store berisi tabel categories, products, dan transactions (plus tabel bawaan Laravel seperti users). Tabel categories sudah berisi tiga kategori, dan tabel users berisi satu akun kasir dengan password yang sudah ter-hash.