Laravel Factories: Laravel Factory Modelleriyle Gerçekçi Test Verileri Oluşturma
Laravel ile uygulama geliştirirken, test iş akışındaki en yaygın darboğazlardan biri anlamlı ve gerçekçi veriler üretmektir. Laravel factory’leri, Eloquent model örnekleri oluşturmak için bir şablon tanımlayan sınıflardır; rastgele ancak yapısal olarak geçerli özellik değerleri üretmek için Faker PHP kütüphanesini kullanır — bu sayede geliştiriciler, veri fixture’larını manuel olarak oluşturmadan veritabanlarını seed’leyebilir ve izole testler yazabilir.
Statik SQL seed dosyaları veya sabit kodlanmış dizilerin aksine, factory’ler birleştirilebilir, durumsal ve ilişki farkındalığına sahiptir. PHPUnit ve Pest test süitleriyle doğrudan entegre olurlar, özellikler için lazy değerlendirmeyi destekler ve tek bir model örneğinden tek bir metot zincirinde binlerce kayda kadar ölçeklenebilirler. Laravel’i bir VPS Hosting ortamında çalıştırıyorsanız, factory’ler özellikle CI/CD pipeline çalıştırmalarında, staging ortam sıfırlamalarında ve tekrarlanabilir, kontrollü veri üretiminin zorunlu olduğu yük testi senaryolarında son derece değerli hale gelir.
Laravel Factory’leri Nedir ve Neden Önemlidir
Laravel factory’leri, Laravel 8’de temelden yeniden tasarlandı. Eski, closure tabanlı `$factory->define()` yaklaşımı, `IlluminateDatabaseEloquentFactoriesFactory`’ı genişleten özel PHP sınıflarıyla değiştirildi. Bu mimari değişiklik, tip güvenliği, IDE otomatik tamamlama ve factory mantığı ile model tanımları arasında daha temiz bir ayrım sağladı.
Her factory sınıfı, model özelliklerinin ilişkisel bir dizisini döndüren bir `definition()` metodu uygular. Factory, `$this->faker` aracılığıyla erişilebilen bir `FakerGenerator` örneğini otomatik olarak çözümler; bu örnek, `name()` ve `safeEmail()`’dan `iban()`, `latitude()`, `uuid()` ve `creditCardNumber()`’a kadar 200’den fazla yerel ayar farkındalıklı veri sağlayıcısını destekler.
Modern factory sisteminin temel özellikleri:
- Sayı, durum ve ilişki yapılandırması için akıcı metot zinciri
- Lazy özellik çözümleme — `definition()` içindeki closure’lar her örnek için yeniden değerlendirilir
- Alana özgü varyasyonları modellemek için factory durumları (örn. askıya alınmış hesaplar, doğrulanmış kullanıcılar)
- Gerektiğinde üst modelleri özyinelemeli olarak oluşturan ilişki factory’leri
- Önceden tanımlanmış özellik kümeleri arasında döngü oluşturmak için Sequence’lar
- Bellekte vs. kalıcı örnekler için `make()` vs `create()`
Ön Koşullar
Factory’leri uygulamadan önce, ortamınızın aşağıdaki gereksinimleri karşıladığından emin olun:
- Laravel 9 veya üzeri (Laravel 8 uyumludur ancak bazı yeni sequence özelliklerinden yoksundur)
- PHP 8.0 veya üzeri
- `.env`’de yapılandırılmış bir veritabanı bağlantısı (MySQL, PostgreSQL veya bellek içi test için SQLite)
- `fakerphp/faker` ile birlikte gelen `laravel/framework` paketi
- Karşılık gelen migration dosyalarına sahip Eloquent modelleri
Laravel’i yönetilen altyapı üzerinde çalıştıran ekipler için, cPanel ile VPS, hem uygulama yığınını hem de veritabanı hizmetlerini birleşik bir arayüzden yönetmek için kullanışlı bir ortam sağlar.
Adım 1: Factory Sınıfı Oluşturma
Bir factory dosyası oluşturmak için Artisan CLI’ı kullanın:
“`bash
php artisan make:factory UserFactory
“`
Bu, `database/factories/UserFactory.php` dosyasını oluşturur. Factory’yi otomatik olarak bir modelle ilişkilendirmek istiyorsanız, `–model` bayrağını geçin:
“`bash
php artisan make:factory UserFactory –model=User
“`
Laravel, factory-model bağlamasını bir adlandırma kuralı aracılığıyla çözümler: `UserFactory`, `AppModelsUser` ile eşlenir. Modelleriniz varsayılan `AppModels` ad alanı dışında yaşadığında bu özellik gerekli olan `protected $model` özelliğini açıkça ayarlayarak bunu geçersiz kılabilirsiniz.
Adım 2: Factory Şablonunu Tanımlama
`database/factories/UserFactory.php` dosyasını açın ve `definition()` metodunu tanımlayın:
“`php
<?php
namespace DatabaseFactories;
use AppModelsUser;
use IlluminateDatabaseEloquentFactoriesFactory;
use IlluminateSupportStr;
use IlluminateSupportFacadesHash;
class UserFactory extends Factory
{
protected $model = User::class;
public function definition(): array
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => Hash::make('password'),
'remember_token' => Str::random(10),
];
}
}
“`
Özellik düzeyinde notlar:
- `$this->faker->unique()->safeEmail()` — `unique()` değiştiricisi, istek başına benzersizlik kaydı tutar. Kullanılabilir benzersiz değerleri tüketirseniz (çok büyük veri kümelerinde nadir ama mümkün), Faker bir `OverflowException` fırlatır. Önbelleği temizlemek için `$this->faker->unique(true)` ile sıfırlayın.
- `Hash::make('password')`, uygulamanın yapılandırılmış hashing sürücüsüne (bcrypt, argon2i, argon2id) saygı gösterdiği için doğrudan `bcrypt()` kullanmaya tercih edilir.
- `email_verified_at => now()`, kullanıcıyı zaten doğrulanmış olarak işaretler. Doğrulanmamış bir hesabı simüle etmek için bu alanı atlayın veya `null` olarak ayarlayın — bu yaygın bir durum varyasyonudur.
Adım 3: Model Örnekleri Oluşturma
3.1 Tek Bir Kaydı Kalıcı Hale Getirme
“`php
$user = AppModelsUser::factory()->create();
“`
Bu, bir `INSERT` ifadesi çalıştırır ve hidrate edilmiş bir `User` Eloquent modeli döndürür. Döndürülen örnek, veritabanı düzeyindeki varsayılanlar veya tetikleyiciler dahil olmak üzere gerçek veritabanı durumunu yansıtır.
3.2 Birden Fazla Kaydı Kalıcı Hale Getirme
“`php
$users = AppModelsUser::factory()->count(10)->create();
“`
10 `User` örneğinden oluşan bir `IlluminateDatabaseEloquentCollection` döndürür. Her kayıt bağımsız olarak üretilmiş Faker değerleri alır — bunlar tek bir örneğin kopyaları değildir.
3.3 Kalıcılık Olmadan Bellekte Örnek
“`php
$user = AppModelsUser::factory()->make();
“`
`make()` metodu, modeli örnekler ve veritabanına dokunmadan özelliklerini doldurur. Bu, model davranışını, özellik dönüşümünü veya accessor/mutator mantığını izole olarak doğrulayan birim testleri için idealdir — testleri hızlı ve veritabanından bağımsız tutar.
3.4 Belirli Özellikleri Geçersiz Kılma
Hem `create()` hem de `make()`, özellik geçersiz kılmalarının bir dizisini kabul eder:
“`php
$user = AppModelsUser::factory()->create([
'email' => 'specific@example.com',
'name' => 'Jane Doe',
]);
“`
Geçersiz kılmalar, `definition()` değerlerine göre öncelik taşır. Bu, bir testin rastgele bir değer yerine belirli, bilinen bir özellik değerine bağlı olduğu durumlarda doğru kalıptır.
Adım 4: Factory Durumları
Durumlar, temel factory tanımında adlandırılmış değişikliklerdir. Tüm factory’yi kopyalamadan farklı alan koşullarını modellemenize olanak tanır.
4.1 Durumları Tanımlama
“`php
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
public function admin(): static
{
return $this->state(fn (array $attributes) => [
'is_admin' => true,
'role' => 'administrator',
]);
}
public function suspended(): static
{
return $this->state(fn (array $attributes) => [
'suspended_at' => now(),
'is_active' => false,
]);
}
“`
4.2 Durumları Uygulama
“`php
// Single state
$adminUser = AppModelsUser::factory()->admin()->create();
// Stacked states — fully composable
$suspendedAdmin = AppModelsUser::factory()->admin()->suspended()->create();
“`
Durumlar, zincirlendiği sırayla değerlendirilir. Sonraki durumlar, önceki durumlardan çakışan anahtarların üzerine yazar; bu da öngörülebilir, katmanlı özellik çözümlemesi sağlar.
Adım 5: Döngüsel Özellik Değerleri için Sequence’lar
Rastgele değerler yerine tanımlanmış bir değer kümesi arasında geçiş yapmanız gerektiğinde `Sequence` kullanın:
“`php
use IlluminateDatabaseEloquentFactoriesSequence;
$users = AppModelsUser::factory()
->count(6)
->state(new Sequence(
['role' => 'editor'],
['role' => 'viewer'],
['role' => 'moderator'],
))
->create();
“`
Bu, sequence dizisi boyunca döngü yaparak rolleri sırayla atar. 6 kullanıcıyla her rol iki kez atanır. Sequence’lar, sayfalama, rol tabanlı erişim kontrolü ve değişken ancak kontrollü veri dağılımlarına bağlı UI render mantığını test etmek için son derece değerlidir.
Adım 6: İlişki Factory’leri
6.1 Belongs-To İlişkisi Tanımlama
`PostFactory.php`’de, üst factory’ye doğrudan bir özellik değeri olarak başvurun:
“`php
<?php
namespace DatabaseFactories;
use AppModelsPost;
use AppModelsUser;
use IlluminateDatabaseEloquentFactoriesFactory;
class PostFactory extends Factory
{
protected $model = Post::class;
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => $this->faker->sentence(),
'body' => $this->faker->paragraphs(3, true),
'slug' => $this->faker->unique()->slug(),
];
}
}
“`
`user_id`, `User::factory()` olarak ayarlandığında, Laravel değerlendirmesini erteler. `user_id` sağlamadan `Post::factory()->create()` çağırırsanız, otomatik olarak yeni bir `User` oluşturulur ve birincil anahtarı kullanılır. Mevcut bir kullanıcı sağlarsanız, iç içe factory tamamen atlanır.
6.2 Mevcut Bir Üst Öğeye Ekleme
“`php
$user = AppModelsUser::factory()->create();
$posts = AppModelsPost::factory()->count(5)->for($user)->create();
“`
`for()` metodu, `user_id` yabancı anahtarını sağlanan modelin birincil anahtarına ayarlayarak gereksiz kullanıcı oluşturmayı önler. Bu, testinizin kapsamında zaten belirli bir kullanıcı olduğunda doğru kalıptır.
6.3 Has-Many İlişkileri
“`php
$userWithPosts = AppModelsUser::factory()
->has(AppModelsPost::factory()->count(3), 'posts')
->create();
“`
Ya da sihirli `hasPosts()` kısayolunu kullanarak (modeldeki ilişki metodu adı aracılığıyla çözümlenir):
“`php
$userWithPosts = AppModelsUser::factory()->hasPosts(3)->create();
“`
Bu, tüm yabancı anahtarlar doğru şekilde çözümlenerek tek, atomik bir işlemde bir kullanıcı ve üç ilişkili gönderi oluşturur.
6.4 Many-to-Many İlişkileri
“`php
$user = AppModelsUser::factory()
->hasAttached(
AppModelsRole::factory()->count(2),
['assigned_at' => now()]
)
->create();
“`
`hasAttached()` metodu, doldurmanız gereken ek pivot özellikler dahil olmak üzere pivot tablo eklemesini yönetir.
Adım 7: Testlerde Factory Kullanımı
7.1 Veritabanı Assertion’larıyla Özellik Testi
“`php
use IlluminateFoundationTestingRefreshDatabase;
class UserTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_be_created_with_factory(): void
{
$user = AppModelsUser::factory()->create();
$this->assertDatabaseHas('users', [
'email' => $user->email,
]);
}
public function test_unverified_user_cannot_access_dashboard(): void
{
$user = AppModelsUser::factory()->unverified()->create();
$response = $this->actingAs($user)->get('/dashboard');
$response->assertRedirect('/email/verify');
}
}
“`
Kritik ayrıntı: Veritabanıyla etkileşime giren test sınıflarında her zaman `RefreshDatabase` veya `DatabaseTransactions` trait’ini kullanın. `RefreshDatabase`, test paketi öncesinde migration’ları yeniden çalıştırır ve her testi geri alınan bir transaction içine sarar; bu sayede testler izole ve idempotent kalır.
7.2 `make()` ile Birim Testi
“`php
public function test_user_full_name_accessor(): void
{
$user = AppModelsUser::factory()->make([
'name' => 'Alice Wonderland',
]);
$this->assertEquals('Alice Wonderland', $user->name);
}
“`
Hiçbir veritabanı etkileşimi gerçekleşmez. Test mikrosaniyeler içinde çalışır ve yüksek frekanslı CI pipeline’ları için uygundur.
Adım 8: Factory’lerle Veritabanı Seeder’ları
8.1 Seeder Oluşturma
“`bash
php artisan make:seeder UserSeeder
“`
8.2 Seeder’ı Uygulama
“`php
<?php
namespace DatabaseSeeders;
use AppModelsUser;
use IlluminateDatabaseSeeder;
class UserSeeder extends Seeder
{
public function run(): void
{
User::factory()
->count(50)
->create();
}
}
“`
8.3 İlişkilerle Bileşik Seeder
“`php
<?php
namespace DatabaseSeeders;
use AppModelsUser;
use AppModelsPost;
use IlluminateDatabaseSeeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
User::factory()
->count(20)
->has(Post::factory()->count(5), 'posts')
->create();
// Create 5 admin users with no posts
User::factory()->count(5)->admin()->create();
}
}
“`
8.4 Seeder’ı Çalıştırma
“`bash
Run a specific seeder
php artisan db:seed –class=UserSeeder
Run all seeders defined in DatabaseSeeder
php artisan db:seed
Migrate fresh and seed in one command (common in staging resets)
php artisan migrate:fresh –seed
“`
`migrate:fresh –seed` komutu, bir staging veya geliştirme veritabanını bilinen, doldurulmuş bir duruma sıfırlamak için standart iş akışıdır. Dedicated Server’larda, bu kalıp temiz ve tekrarlanabilir bir ortam sağlamak için QA döngülerinden önce sıklıkla kullanılır.
Gelişmiş Kalıplar ve Uç Durumlar
Lazy Özellikler ve Bağımlı Değerler
`definition()` içindeki Faker değerleri her factory çağrısı için yeniden değerlendirilir. Ancak, bir özelliğin diğerine bağlı olmasına ihtiyaç duyarsanız, bir closure kullanın:
“`php
public function definition(): array
{
$firstName = $this->faker->firstName();
$lastName = $this->faker->lastName();
return [
'first_name' => $firstName,
'last_name' => $lastName,
'email' => strtolower("{$firstName}.{$lastName}@example.com"),
'username' => strtolower("{$firstName}{$lastName}") . $this->faker->numerify('###'),
];
}
“`
Bu, `email` ve `username`’ın aynı isim değerlerinden türetilmesini sağlayarak dahili olarak tutarlı kayıtlar üretir.
`unique()` Taşma Tuzağından Kaçınma
Tek bir factory çağrısında büyük veri kümeleri (10.000’den fazla kayıt) oluştururken, `$this->faker->unique()->safeEmail()` Faker’ın benzersizlik havuzunu tüketebilir ve bir `OverflowException` fırlatabilir. Bunu, oluşturulan değere bir UUID veya zaman damgası ekleyerek azaltın:
“`php
'email' => $this->faker->safeEmail() . '.' . $this->faker->uuid() . '@test.com',
“`
Bu, Faker’ın dahili benzersizlik kaydına güvenmeden ölçekte benzersizliği garanti eder.
Factory Callback’leri: `afterMaking` ve `afterCreating`
Basit bir özellik olarak ifade edilemeyen oluşturma sonrası mantığı gerçekleştirmek için callback’leri kullanın:
“`php
public function configure(): static
{
return $this->afterCreating(function (User $user) {
$user->profile()->create([
'bio' => $this->faker->paragraph(),
'avatar' => $this->faker->imageUrl(200, 200, 'people'),
]);
});
}
“`
`configure()` metodu, factory örneklendiğinde bir kez çağrılır. `afterCreating`, model kalıcı hale getirildikten sonra çalışır ve üst öğenin birincil anahtarını gerektiren ilişkili modeller oluşturmak için uygundur.
E-posta İşlevselliğini Test Etme
Factory’ler e-posta adresleriyle kullanıcılar oluşturduğunda, e-posta gönderimini doğrulayan entegrasyon testleri, test e-postalarının gerçek adreslere yanlışlıkla iletilmesini önleyen sandbox SMTP sunucusuyla yapılandırılmış özel bir E-posta Hosting ortamından yararlanır.
`create()` vs `make()` vs `makeMany()` vs `createMany()` — Karşılaştırma
| Metot | DB’ye Kaydeder | Döndürür | En İyi Kullanım Durumu |
|---|---|---|---|
| — | — | — | — |
| `create()` | Evet | Tek model örneği | Özellik testleri, seeder’lar |
| `create(['key' => 'val'])` | Evet | Tek model örneği | Belirli bilinen değerler gerektiren testler |
| `count(n)->create()` | Evet | n modelden oluşan koleksiyon | Toplu seed’leme, sayfalama testleri |
| `make()` | Hayır | Tek model örneği | Birim testleri, accessor/mutator testi |
| `make(['key' => 'val'])` | Hayır | Tek model örneği | Kontrollü özelliklerle hızlı birim testleri |
| `count(n)->make()` | Hayır | n modelden oluşan koleksiyon | Bellekte koleksiyon testi |
| `createMany([…])` | Evet | Koleksiyon | Farklı özellik kümeleriyle toplu oluşturma |
| `makeMany([…])` | Hayır | Koleksiyon | Toplu bellekte örnekler |
Factory Durumları vs. Özellik Geçersiz Kılmaları — Hangisini Ne Zaman Kullanmalı
| Senaryo | Önerilen Yaklaşım |
|---|---|
| — | — |
| Yeniden kullanılabilir alan koşulu (örn. “yönetici kullanıcı”) | Adlandırılmış durum metodu |
| Teste özgü tek seferlik değer | `create()`’da özellik geçersiz kılma |
| Önceden tanımlanmış bir küme arasında döngü | `Sequence` |
| Oluşturma sonrası yan etkiler | `afterCreating` callback’i |
| Bağımlı özellik değerleri | `definition()`’da closure tabanlı özellikler |
| İlişki doldurma | `has()`, `for()`, `hasAttached()` |
Pratik Karar Kontrol Listesi
Bir factory yazmadan veya factory kullanan bir test oluşturmadan önce şu kontrol noktalarını gözden geçirin:
- Test veritabanına bağımlı mı? Değilse, `make()` kullanın ve `RefreshDatabase` yükünden kaçının.
- Test belirli bir özellik değeri gerektiriyor mu? `create()`’a geçersiz kılma olarak geçirin — factory `definition()`’ında sabit kodlamayın.
- Rol tabanlı davranışı mı test ediyorsunuz? Birden fazla test dosyasına `create(['is_admin' => true])` dağıtmak yerine adlandırılmış durumlar tanımlayın.
- Bir staging ortamını mı seed’liyorsunuz? `migrate:fresh –seed` kullanın ve `DatabaseSeeder`’ınızın tüm alt seeder’ları doğru bağımlılık sırasıyla (üst öğeler çocuklardan önce) oluşturduğundan emin olun.
- 5.000’den fazla kayıt mı oluşturuyorsunuz? Yüksek kardinaliteli alanlarda `unique()` kullanmaktan kaçının; bunun yerine UUID eklenmiş değerler kullanın.
- Modellerinizin harici servislere ulaşan `afterCreating` callback’leri var mı? Test kurulumunuzda bu servisleri mock’layın veya callback’i tamamen atlamak için `make()` kullanın.
- Testleri paralel olarak mı çalıştırıyorsunuz? Paralel worker’lar arasındaki migration çakışmalarını önlemek için `DatabaseTransactions` yerine `RefreshDatabase` kullanın veya worker başına ayrı veritabanı bağlantıları yapılandırın.
Birden fazla Laravel uygulamasını ortamlar genelinde yöneten ekipler için, VPS Kontrol Panelleri, büyük seed’leme işlemleri ve test çalıştırmaları sırasında veritabanı performansını izlemek için gereken altyapı görünürlüğünü sağlar.
SSS
Laravel factory’lerinde `create()` ile `make()` arasındaki fark nedir?
`create()`, modeli veritabanına kaydeder ve hidrate edilmiş bir Eloquent örneği döndürür. `make()`, herhangi bir veritabanı etkileşimi olmadan modeli bellekte oluşturur. Testleri hızlı ve izole tutmak için saf birim testlerinde `make()` kullanın; testin veritabanı durumunu doğrulaması gerektiğinde `create()` kullanın.
Laravel factory’leri polimorfik ilişkileri yönetebilir mi?
Evet. `morphTo` ilişkisini, `*_type` ve `*_id` morph sütunlarını doğrudan factory `definition()`’ında ayarlayarak veya üst model kalıcı hale getirildikten sonra polimorfik ilişkileri eklemek için `afterCreating` kullanarak tanımlayın. Yerleşik bir `hasMorphedByMany()` kısayolu yoktur, bu nedenle açık özellik ayarı en güvenilir yaklaşımdır.
Testler sırasında factory tarafından oluşturulan e-postaların gönderilmesi nasıl önlenir?
`.env.testing` dosyanızda `MAIL_MAILER=array` veya `MAIL_MAILER=log` ayarlayın. Bu, tüm postaları Laravel’in dizi veya log sürücüsü üzerinden yönlendirerek mesajları bellekte yakalar veya bir SMTP sunucusuna göndermeden log dosyasına yazar. Ardından testlerinizde `Mail::assertSent()` üzerinde assertion yapabilirsiniz.
Büyük veri kümelerinde `faker->unique()->safeEmail()` neden `OverflowException` fırlatır?
Faker’ın `unique()` değiştiricisi, daha önce oluşturulan değerlerin bellekte bir kaydını tutar. Yapısal olarak geçerli benzersiz değerlerin havuzu tükendiğinde — on binlerce kayıtta gerçekleşebilir — `OverflowException` fırlatır. Çözüm, temel e-posta değerine bir UUID veya rastgele dize ekleyerek Faker’ın kaydına güvenmeden benzersizliği sağlamaktır.
Factory’ler üretim seeder’larında kullanılmalı mı?
Factory’ler geliştirme ve test ortamları için tasarlanmıştır. Üretim seed’lemesi için (örn. arama tablolarını, varsayılan rolleri veya yapılandırma kayıtlarını doldurma), sabit kodlanmış, deterministik değerlere sahip özel seeder sınıfları kullanın. Faker’a bağlı factory’ler hiçbir zaman bir üretim veritabanına karşı çalıştırılmamalıdır; çünkü öngörülemeyen, denetlenemeyen veriler ortaya çıkarırlar.
