Data uji yang dapat direproduksi adalah prasyarat latihan database yang baik, karena contoh query hanya bermakna apabila isinya selalu sama. Topik ini membahas dua perkakas Laravel untuk keperluan itu, yaitu factory yang membuat objek model dengan nilai bawaan dan seeder yang mengisi database secara massal. Keduanya diterapkan pada aplikasi catatan: dua factory dibuat untuk model Category dan Note, lalu seeder bawaan ditulis ulang agar menghasilkan tiga kategori dan beberapa catatan yang isinya tetap sama setiap kali dijalankan.
Factory sebagai pabrik data#
Factory adalah kelas yang mendefinisikan keadaan bawaan (default state) sebuah model dalam bentuk array. Definisi itu ditulis pada metode definition(), sehingga setiap pemanggilan menghasilkan atribut dari nilai yang sama. Factory tidak berdiri sendiri: kelas model perlu menggunakan trait HasFactory agar metode statis factory() tersedia.
Dari definisi tersebut, factory menyediakan dua metode utama. Metode make() hanya membuat objek model di memori tanpa menyentuh database, sehingga cocok untuk menguji bentuk data, sedangkan metode create() benar-benar menyimpan baris baru ke tabel. Keduanya menerima array atribut yang menimpa nilai bawaan, misalnya Note::factory()->create(['judul' => 'Rencana belajar']) menghasilkan catatan dengan judul yang ditentukan dan isi bawaan dari definition().
Dokumentasi factory biasanya mengisi definition() dengan bantuan library data acak. Pada aplikasi latihan ini nilai acak sengaja dihindari.
Membuat factory#
Factory dibuat dengan perintah make:factory yang menerima nama modelnya melalui opsi --model.
# tidak-dijalankan
# membuat dua berkas factory untuk model Category dan Note
cd ~/aplikasi-catatan
php artisan make:factory CategoryFactory --model=Category
php artisan make:factory NoteFactory --model=NotePerintah tersebut tidak dieksekusi ulang pada contoh ini karena kedua berkasnya sudah ada. Keluaran asli dari perintah itu adalah sebagai berikut.
INFO Factory [database/factories/CategoryFactory.php] created successfully.
INFO Factory [database/factories/NoteFactory.php] created successfully. Factory untuk kategori hanya memuat satu nilai bawaan, yaitu nama kategori. Nilai itu dipilih sebagai kategori paling umum pada aplikasi catatan pribadi, sedangkan nama-nama lain diberikan melalui atribut tambahan saat pemanggilan.
// tidak-dijalankan
<?php
namespace Database\Factories;
use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Category>
*/
class CategoryFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'nama' => 'Pribadi',
];
}
}Factory catatan mendefinisikan satu pasangan judul dan isi yang bertema pencatatan harian. Kelompok atribut bernama sequence digunakan saat pemanggilan untuk memberi variasi judul dan isi tanpa mengubah definisinya.
// tidak-dijalankan
<?php
namespace Database\Factories;
use App\Models\Note;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Note>
*/
class NoteFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'judul' => 'Catatan harian',
'isi' => 'Menandai tugas yang selesai dan menyiapkan prioritas untuk hari berikutnya.',
];
}
}Menyusun seeder#
Seeder adalah kelas yang menjalankan langkah pengisian database ketika dipanggil. Laravel menyediakan satu kelas bernama DatabaseSeeder pada direktori database/seeders, dan kelas itulah yang ditulis ulang untuk aplikasi catatan. Tiga kategori dibuat dengan firstOrCreate agar seeder tetap aman dijalankan ulang: kategori yang sudah ada di database tidak dibuat lagi. Catatan baru dibuat melalui factory hanya apabila tabel notes masih kosong, sehingga menjalankan seeder berkali-kali tidak menumpuk data.
// tidak-dijalankan
<?php
namespace Database\Seeders;
use App\Models\Category;
use App\Models\Note;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void
{
$kerja = Category::firstOrCreate(['nama' => 'Kerja']);
$belajar = Category::firstOrCreate(['nama' => 'Belajar']);
$pribadi = Category::firstOrCreate(['nama' => 'Pribadi']);
if (Note::query()->exists()) {
return;
}
Note::factory()->count(2)->sequence(
['judul' => 'Catatan rapat pekanan', 'isi' => 'Mencatat keputusan rapat beserta tindak lanjut dan penanggung jawabnya.'],
['judul' => 'Daftar tugas hari Senin', 'isi' => 'Memilah pekerjaan prioritas dan menandai tugas yang sudah selesai.'],
)->create(['category_id' => $kerja->id]);
Note::factory()->count(3)->sequence(
['judul' => 'Rencana belajar Laravel', 'isi' => 'Menyelesaikan modul database dan membuat ringkasan singkat untuk setiap topik.'],
['judul' => 'Ringkasan materi database', 'isi' => 'Merangkum poin migration, factory, dan relasi dalam satu halaman.'],
['judul' => 'Ide latihan pemrograman', 'isi' => 'Membuat penghitung kata sederhana dengan riwayat hasil yang tersimpan.'],
)->create(['category_id' => $belajar->id]);
Note::factory()->count(2)->sequence(
['judul' => 'Daftar belanja mingguan', 'isi' => 'Beras, telur, sayuran segar, buah, dan kopi untuk stok dua minggu.'],
['judul' => 'Rencana menu makan sepekan', 'isi' => 'Menyusun menu tujuh hari agar bahan belanjaannya terpakai habis.'],
)->create(['category_id' => $pribadi->id]);
}
}Metode count() menentukan jumlah objek yang dibuat, sedangkan setiap butir sequence menimpa judul dan isi untuk satu baris secara berurutan. Atribut category_id diberikan pada create() sehingga seluruh baris dalam satu panggilan masuk ke kategori yang sama.
Menjalankan seeder#
Seeder dijalankan dengan perintah db:seed. Opsi --force diperlukan pada lingkungan yang tidak interaktif, misalnya saat penerapan aplikasi, karena tanpa opsi itu Artisan meminta konfirmasi terlebih dahulu.
# mengisi database dengan data seed
cd ~/aplikasi-catatan
php artisan db:seed --force INFO Seeding database.Isi hasil seed dapat diperiksa dengan Artisan Tinker. Query berikut menyaring tiga kategori hasil seed dan menghitung catatan yang judulnya berasal dari seed, sehingga keluarannya tetap sama meskipun latihan lain menambah data sendiri.
# menampilkan ringkasan hasil seed
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use App\Models\Note;
echo "Kategori: " . Category::whereIn("nama", ["Kerja", "Belajar", "Pribadi"])->orderBy("nama")->pluck("nama")->implode(", ") . PHP_EOL;
echo "Jumlah catatan seed: " . Note::whereIn("judul", ["Catatan rapat pekanan", "Daftar tugas hari Senin", "Rencana belajar Laravel", "Ringkasan materi database", "Ide latihan pemrograman", "Daftar belanja mingguan", "Rencana menu makan sepekan"])->count() . PHP_EOL;
'Kategori: Belajar, Kerja, Pribadi
Jumlah catatan seed: 7Cara kedua menggunakan factory tanpa menyimpan data. Metode make() hanya mengembalikan objek dengan nilai dari definition(), sehingga aman dipanggil berulang kali untuk melihat bentuk data bawaan.
# membuat objek model tanpa menyimpannya ke database
cd ~/aplikasi-catatan
php artisan tinker --execute='
echo App\Models\Note::factory()->make()->judul . PHP_EOL;
echo App\Models\Category::factory()->make()->nama . PHP_EOL;
'Catatan harian
PribadiKetika seluruh isi database perlu dikembalikan ke keadaan seed, migrate:fresh dan db:seed dapat dijalankan berurutan, atau digabungkan menjadi satu perintah melalui opsi --seed pada migrate:fresh. Perintah gabungan itu menghapus seluruh tabel, menjalankan ulang migration, lalu memanggil seeder, sehingga tidak ada dua langkah terpisah. Cara ini cocok digunakan pada lingkungan pengembangan, ketika eksperimen membuat isi database semakin jauh dari keadaan awalnya.
Kesalahan umum#
Kesalahan pertama adalah memanggil Note::factory() padahal modelnya belum menggunakan trait HasFactory. Laravel menampilkan BadMethodCallException dengan pesan call to undefined method, dan perbaikannya adalah menambahkan use HasFactory; beserta impor Illuminate\Database\Eloquent\Factories\HasFactory pada kelas model.
Kesalahan kedua adalah menjalankan seeder tanpa perlindungan duplikasi, lalu menemukan tabel yang penuh duplikasi kategori setiap kali perintah diulang. Pola firstOrCreate dan pemeriksaan tabel kosong pada seeder aplikasi contoh menyelesaikan masalah itu, dan pola yang sama layak digunakan pada seeder lain.
Kesalahan ketiga adalah menghapus isi DatabaseSeeder sepenuhnya lalu membagi seed ke banyak kelas tanpa mendaftarkannya. Seeder tambahan hanya dijalankan apabila dipanggil dari DatabaseSeeder atau disebut langsung melalui php artisan db:seed --class=NamaSeeder, sehingga lupa mendaftar membuat sebagian data tidak pernah terisi.
Kesalahan keempat adalah menjalankan migrate:fresh --seed pada database produksi. Perintah itu menghapus seluruh tabel beserta isinya sebelum membangun ulang skema, sehingga data pengguna yang sudah nyata hilang tanpa dapat dikembalikan. Pembangunan ulang total hanya lazim pada database pengembangan atau database uji, sedangkan pada produksi skema diperbarui secara bertahap dengan php artisan migrate --force yang hanya menerapkan migration yang belum berjalan.
Ringkasan#
- Factory mendefinisikan keadaan bawaan model pada
definition(), denganmake()untuk objek di memori dancreate()untuk menyimpan baris baru. - Atribut yang diberikan saat pemanggilan menimpa nilai bawaan, dan
sequencemenyediakan variasi berurutan untuk sekumpulan baris. - Seeder memusatkan langkah pengisian data, dan pola
firstOrCreateserta pemeriksaan tabel kosong membuat seeder aman dijalankan ulang. php artisan db:seed --forcemengisi database, sedangkanmake()pada Tinker menampilkan data bawaan tanpa menyentuh tabel.
Referensi#
- Seeding — Laravel — Dokumentasi resmi seeder, pemanggilan db:seed, dan penulisan DatabaseSeeder.
- Eloquent Factories — Laravel — Dokumentasi definisi factory, atribut tambahan, dan sequence.