Seeder mengisi data yang nilainya tetap, tetapi pengembangan sering butuh kebalikannya: puluhan baris data variatif untuk menguji pagination, daftar, atau laporan. Menulis 30 baris insert() manual adalah siksaan, dan datanya cenderung monoton sehingga bug tampilan tidak ketahuan. Model factory menyelesaikan ini dengan satu class yang tahu cara membangun satu instance model beserta data masuk akalnya — dari sana kita bisa memproduksi satu, tiga, atau tiga ratus baris dengan satu rantai method. Chapter ini membahas anatomi factory, state, make() versus create(), sequence, dan sekilas factory relasi.
Membuat Factory #
Factory tersimpan di database/factories/ dan dibuat lewat artisan. Perintah berikut membuat factory untuk model Post:
php artisan make:factory PostFactory --model=PostOutput yang Dihasilkan:
INFO Factory [database/factories/PostFactory.php] created successfully. Berkas hasilnya berisi class kosong yang mewarisi Factory dengan satu method definition(). Agar factory bisa dipanggil lewat Post::factory(), model Post cukup memakai trait HasFactory.
definition() dengan fake() #
Method definition() adalah resep satu baris data: setiap kuncinya adalah nama kolom, dan nilainya bisa dihasilkan pustaka Faker
. Berikut PostFactory lengkap untuk tabel posts yang kita bangun di chapter Column Types
:
<?php
namespace Database\Factories;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Post>
*/
class PostFactory extends Factory
{
public function definition(): array
{
return [
'user_id' => User::factory(),
'judul' => fake()->sentence(4),
'slug' => fake()->unique()->slug(),
'isi' => fake()->paragraphs(3, true),
'views' => fake()->numberBetween(0, 500),
'harga' => fake()->randomFloat(2, 10000, 1000000),
'published' => false,
'meta' => ['sumber' => 'kursus-laravel'],
];
}
public function published(): static
{
return $this->state(fn (array $attributes) => [
'published' => true,
'views' => fake()->numberBetween(100, 1000),
]);
}
}Perhatikan dua pola nilai di dalamnya. Nilai judul dan isi dihasilkan fake() sehingga berbeda pada setiap baris — inilah yang membuat data uji terasa hidup. Sebaliknya, user_id bernilai User::factory(): nilai ini bukan angka, melainkan factory lain yang otomatis membuat user baru dan memakai id-nya setiap kali satu post dibuat.
Locale Faker: Realisme vs Determinisme #
Secara bawaan Faker memakai locale en_US, sehingga nama yang dihasilkan berformat Barat. Konfigurasi APP_FAKER_LOCALE mengubahnya:
APP_FAKER_LOCALE=id_IDDengan locale id_ID, panggilan fake()->name() menghasilkan nama Indonesia (misalnya “Widya Usamah”), fake()->phoneNumber() mengikuti format Indonesia, dan seterusnya. Namun ada trade-off penting untuk buku pelajaran: data Faker berbeda di setiap eksekusi. Karena itu, contoh-contoh Course ini menetapkan nilai eksplisit pada kolom yang ingin dicetak — misalnya ->create(['judul' => 'Belajar Factory']) — supaya output yang ditampilkan selalu bisa dicocokkan pembaca.
make() versus create() #
Factory menawarkan dua tahap yang berbeda tujuan. Method make() membangun instance model di memori tanpa menyimpannya — cocok untuk menguji validasi atau tampilan form. Method create() menyimpannya ke database. Skrip berikut membandingkan keduanya:
echo 'Sebelum: ', Post::count(), ' post, ', User::count(), " user\n";
$post = Post::factory()->make([
'judul' => 'Belajar Model Factory',
'slug' => 'belajar-model-factory',
]);
echo 'make(): id=', var_export($post->id, true),
' | judul=', $post->judul,
' | published=', $post->published ? 'true' : 'false', "\n";
echo 'Setelah make(): ', Post::count(), ' post, ', User::count(), " user\n";
$post = Post::factory()->create([
'judul' => 'Belajar Model Factory',
'slug' => 'belajar-model-factory',
]);
echo 'create(): id=', $post->id,
' | Setelah create(): ', Post::count(), ' post, ', User::count(), " user\n";Output yang Dihasilkan:
Sebelum: 0 post, 1 user
make(): id=NULL | judul=Belajar Model Factory | published=false
Setelah make(): 0 post, 2 user
create(): id=1 | Setelah create(): 1 post, 3 userModel hasil make() punya id NULL dan jumlah post tetap nol — tidak ada INSERT untuk modelnya. Namun ada satu kejutan pada output di atas yang layak dipahami: jumlah user naik dari satu menjadi dua padahal kita hanya make(). Factory User yang bersarang di user_id tetap dijalankan dan tersimpan ke database bahkan saat make() — inilah perilaku Laravel, factory bersarang selalu dibuat permanen agar relasi model valid. Satu user kemudian dibuat lagi oleh create(), sehingga totalnya tiga.
count() dan sequence() #
Untuk banyak baris sekaligus, rantai count(). Method sequence() lalu memberi nilai berbeda secara bergantian pada baris-baris tersebut — berguna saat data uji harus spesifik dan mudah dikenali:
$posts = Post::factory()->count(3)->published()->sequence(
['judul' => 'Belajar Factory', 'slug' => 'belajar-factory'],
['judul' => 'Latihan Query Builder', 'slug' => 'latihan-query-builder'],
['judul' => 'Migrasi Data Lama', 'slug' => 'migrasi-data-lama'],
)->create();
foreach ($posts as $post) {
echo $post->id, '. ', $post->judul,
' | published=', $post->published ? 'true' : 'false', "\n";
}
echo 'Total sekarang: ', Post::count(), " post\n";Output yang Dihasilkan:
2. Belajar Factory | published=true
3. Latihan Query Builder | published=true
4. Migrasi Data Lama | published=true
Total sekarang: 4 postRantai ->count(3)->published() memanggil state published() pada ketiga baris, lalu sequence() menimpa judul dan slug bergiliran — baris pertama memakai pasangan pertama, dan seterusnya. Kolom lain tetap diisi definition(), jadi kita hanya menimpa yang perlu.
Pratinjau Factory Relasi #
Pola user_id => User::factory() sudah otomatis, tetapi arah sebaliknya lebih ekspresif: buat user beserta isiannya dalam satu rantai. Method has() pada factory user membuat post-post miliknya:
$user = User::factory()
->has(Post::factory()->count(3)->published())
->create(['name' => 'Siti', 'email' => '[email protected]']);
echo $user->name, ' (id=', $user->id, ') memiliki ',
$user->posts()->count(), " post, semuanya published\n";Output yang Dihasilkan:
Siti (id=7) memiliki 3 post, semuanya publishedSatu rantai menghasilkan satu user Siti plus tiga post miliknya — user_id setiap post otomatis mengacu pada Siti, tanpa factory user liar per baris. Pola ini menjadi tulang punggung penyiapan data pengujian, dan kita perdalam lagi di Section Eloquent Relationships
setelah relasi model dibahas tuntas.
Dengan seeder untuk data tetap dan factory untuk data variatif, isi database sudah berada dalam kendali kita. Satu masalah terakhir mengintai: bagaimana bila rangkaian query harus berjalan semua atau tidak sama sekali? Jawabannya adalah transaksi database, topik penutup section ini.