11 β Seeder, Factory va Tinker¶
β¬ οΈ Oldingi: 10 β Query Builder va ilg'or Eloquent Β· π README Β· Keyingi: 12 β Validatsiya β‘οΈ
Bu bobda: ishlab chiqish va test uchun kerak bo'ladigan namuna ma'lumotni qo'lda emas, kod bilan to'ldirishni o'rganamiz. Seeder ("ekuvchi") nima ekanini,
DatabaseSeederqanday dirijyorlik qilishini, Model Factory'ningdefinition()ichida Faker orqali soxta-real ma'lumot tuzishni,factory()->count()->create()bilan o'nlab qator yaratishni, state bilan turli ko'rinishlarni (published/draft), relation factory (has,for,hasAttached) bilan bog'langan ma'lumotni,sequence,recycle,afterCreatingqulayliklarini,migrate:fresh --seedbilan bazani bir buyruqda qayta tiklashni va nihoyat Tinker'da hamma narsani jonli sinashni ko'rib chiqamiz. Oxirida factory'ning test uchun nega muhimligiga ko'prik tashlaymiz (22-bob).
Muammo¶
10-bobgacha User, Post, Comment, Tag modellarini va ular orasidagi munosabatlarni tuzdik. Endi blogni brauzerda ochib, "postlar ro'yxati" sahifasini ko'rmoqchimiz. Lekin baza bo'sh. Sahifa qanday ko'rinishini tekshirish uchun ma'lumot kerak: 20-30 ta post, ularning mualliflari, izohlari, taglari.
Sof yo'l β Tinker'da yoki SQL'da qo'lda yozish:
Post::create(['user_id' => 1, 'title' => 'Birinchi post', 'body' => 'Matn...']);
Post::create(['user_id' => 1, 'title' => 'Ikkinchi post', 'body' => 'Yana matn...']);
Post::create(['user_id' => 2, 'title' => 'Uchinchi post', 'body' => '...']);
// ... va shu zaylda yana 27 marta
30 ta post uchun 30 qator, har biriga sarlavha va matn o'ylab topish kerak. Sahifalashni (pagination) sinash uchun 100 ta kerak bo'lsa-chi? Jamoadoshingiz loyihani klonlab oldi β uning bazasi yana bo'sh, hammasini boshqatdan yozadi. Bazani tozalab qayta boshlasangiz β yana noldan. Bu zerikarli, takror va xatoga moyil ish.
Laravel buni ikki vosita bilan hal qiladi:
- Factory ("zavod") β modelning bitta namunaviy nusxasini qanday yasashni biladigan "qolip". Bir marta yozasiz, keyin undan istalgancha nusxa chiqarasiz.
- Seeder ("ekuvchi") β shu factory'larni ishga solib, bazaga ma'lumot "ekadigan" sinf. Bitta buyruq β
php artisan db:seedβ va baza to'ladi.
Natijada 100 ta post bitta qatorga sig'adi:
Bu bob aynan shu ikki vositani β va ularni sinaydigan Tinker'ni β o'rgatadi.
Faktorisiz: dastlabki tushuncha¶
Avval eng sodda holatni ko'ramiz: hech qanday soxta ma'lumotsiz, shunchaki seeder. Seeder β Database\Seeders papkasidagi oddiy sinf. Yangi Laravel loyihasida bitta tayyor seeder bor β database/seeders/DatabaseSeeder.php. Bu bosh seeder: php artisan db:seed aynan uning run() metodini chaqiradi.
Ochib ko'ramiz (yangi loyihada taxminan shunday turadi):
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
}
}
run() ichiga oddiy Eloquent kodi yozsa bo'ladi. Masalan, bir nechta tagni qo'lda "ekamiz":
public function run(): void
{
Tag::create(['name' => 'php']);
Tag::create(['name' => 'laravel']);
Tag::create(['name' => 'eloquent']);
}
Ishga tushirish:
π Seeder β shunchaki "men ishga tushganda quyidagi ma'lumotni bazaga yoz" deydigan joy. Ichida xohlagan Eloquent kodingizni yozasiz. Lekin har bir qatorni qo'lda yozish β yana o'sha eski muammo. Shuning uchun seeder'ning haqiqiy kuchi factory bilan birga ochiladi.
π‘ db:seed bosh DatabaseSeederni chaqiradi. Boshqa, alohida seederni ishga tushirish uchun --class bayrog'i bor:
Model Factory β namuna qolipi¶
Factory ("zavod, qolip") β modelning bitta soxta nusxasini qanday tuzishni belgilaydigan sinf. U database/factories papkasida yashaydi. Yangi Laravel loyihasida bitta tayyor factory bor: UserFactory.
Yangi factory yasash:
Bu database/factories/PostFactory.php faylini yaratadi:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
public function definition(): array
{
return [
//
];
}
}
Eng muhim joy β definition() metodi. U bitta qatorning ustunlarini massiv ko'rinishida qaytaradi. Bu yerni biz to'ldiramiz:
public function definition(): array
{
return [
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'published' => fake()->boolean(70), // 70% ehtimol bilan true
];
}
O'qilishi: "har safar bitta Post yasalganda β sarlavha sifatida soxta jumla, matn sifatida 3 ta soxta abzas, published sifatida 70% ehtimolda true qo'y". fake() β bu Faker, keyingi bo'limning qahramoni.
π definition() ustunlar uchun standart qiymatlarni beradi. Ya'ni "agar boshqacha aytmasang, qator shunday bo'ladi". Yaratishda bu qiymatlarni ustiga yozish mumkin (buni darrov ko'ramiz).
π‘ Modelni yasashda -f bayrog'i factory'ni ham birga yaratadi: php artisan make:model Post -mf. Yoki hammasi birga: php artisan make:model Post -a (model, migration, factory, seeder, controller).
Model factory'ni "ko'rsin" β HasFactory¶
Factory ishlashi uchun model HasFactory trait'ini ishlatishi kerak. Yangi modellarda u odatda allaqachon turadi, lekin tekshirib qo'ying:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'title', 'body', 'published'];
}
π HasFactory β Post::factory() metodini modelga qo'shadigan trait. U bo'lmasa, Post::factory() chaqirilganda "method not found" xatosi chiqadi. Konvensiya bo'yicha Eloquent Post modeliga PostFactory factory'sini o'zi topadi (model nomi + Factory).
Faker β soxta, lekin real ko'rinadigan ma'lumot¶
definition() ichidagi fake() β Faker kutubxonasi. Uning vazifasi β har xil turdagi soxta, lekin haqiqatga o'xshash ma'lumot tuzish: ismlar, email'lar, manzillar, jumlalar, sanalar. "asdfgh" yoki "test123" emas, balki "Aziz Karimov", "qui.dolorem@example.org" kabi ko'rinadigan qiymatlar.
Nega muhim? Soxta ma'lumot realga o'xshasa, UI'ni rost sharoitda sinaysiz: uzun ismlar joyiga sig'adimi, uzun matn dizaynni buzmaydimi. "aaa" bilan bularning hech birini ko'rmaysiz.
Eng ko'p ishlatiladigan Faker metodlari:
| Chaqiruv | Misol natija |
|---|---|
fake()->name() |
Aziz Karimov |
fake()->firstName() |
Malika |
fake()->email() |
qui.dolorem@example.org |
fake()->unique()->safeEmail() |
takrorlanmas, xavfsiz email |
fake()->sentence() |
Qui dolorem ipsum quia. |
fake()->paragraph() |
bitta abzas matn |
fake()->paragraphs(3, true) |
3 abzas, bitta matn satr sifatida |
fake()->text(200) |
200 belgigacha matn |
fake()->word() |
dolorem |
fake()->boolean(70) |
70% ehtimolda true |
fake()->numberBetween(1, 100) |
1..100 oralig'idagi son |
fake()->randomElement(['a', 'b', 'c']) |
ro'yxatdan tasodifiy biri |
fake()->date() |
2019-04-12 |
fake()->dateTimeBetween('-1 year', 'now') |
o'tgan yil ichidagi vaqt |
fake()->imageUrl() |
rasm URL'i |
fake()->uuid() |
tasodifiy UUID |
π fake()->unique() β keyingi metod takrorlanmas qiymat qaytarishini kafolatlaydi. Email yoki nom ustunida unique indeks bo'lsa, buni qo'ying β aks holda ikki bir xil email yaratilib, baza xato beradi.
π‘ Faker o'zbekcha emas β standart "lorem ipsum" lotincha matn va ko'proq ingliz ismlari beradi. Bu yomon emas: maqsad ma'lumot realga o'xshashi, til esa muhim emas (test uchun ekan). Aniq qiymatlar kerak bo'lsa, randomElement bilan o'zingiz ro'yxat berasiz:
π Faker β Laravel'ning ichki qismi emas, alohida kutubxona (fakerphp/faker), lekin yangi Laravel loyihasida allaqachon o'rnatilgan turadi. fake() yordamchisi shu kutubxonaga qisqa yo'l. Factory ichida $this->faker ham ishlaydi, lekin yangi uslubda fake() afzal.
factory()->create() β qatorlarni yaratish¶
Factory tayyor. Endi undan haqiqiy qatorlar chiqaramiz. Asosiy metod β create(): u qatorni bazaga yozadi va model obyektini qaytaradi.
// Bitta post (factory standart qiymatlari bilan):
$post = Post::factory()->create();
echo $post->id; // baza bergan yangi id
echo $post->title; // faker tuzgan soxta sarlavha
Bir nechtasini birdan β count():
// 10 ta post:
$posts = Post::factory()->count(10)->create();
echo $posts->count(); // 10 β Collection qaytadi
Har bir post boshqacha bo'ladi, chunki definition() har chaqirilganda Faker yangi qiymat beradi.
π make() β create()ning "bazaga yozmaydigan" varianti. U model obyektini xotirada tayyorlaydi, lekin INSERT yubormaydi. "Obyekt kerak, lekin saqlash shart emas" holatlarida (ko'pincha testda) asqotadi:
$post = Post::factory()->make(); // obyekt bor, lekin bazada yo'q
$post->id; // null β hali saqlanmagan
Standart qiymatlarni ustiga yozish¶
definition() faqat standartni beradi. Yaratishda ayrim ustunlarni o'zingiz belgilashingiz mumkin β create() (yoki make()) ga massiv berasiz:
// Sarlavhasi aniq, qolgani factory'dan:
$post = Post::factory()->create([
'title' => 'Maxsus sarlavha',
'published' => true,
]);
Bu yerda title va published siz bergan qiymatni oladi, body esa factory'ning faker'idan keladi. Bu juda muhim: factory "umumiy holat"ni beradi, siz esa testda kerakli bitta detalni aniqlaysiz.
β
Post::factory()->create(['published' => true]) β faqat kerakli ustunni belgilang, qolganini factory hal qilsin.
β Har bir ustunni qo'lda yozish β factory'ning butun ma'nosini yo'qotadi.
π‘ Eng ko'p ishlatiladigan namuna β admin foydalanuvchini aniq email bilan, qolganini faker bilan yaratish:
State β modelning turli ko'rinishlari¶
Ko'pincha modelning bir nechta "holati" bo'ladi: post e'lon qilingan yoki qoralama; user tasdiqlangan yoki tasdiqlanmagan. Har safar create(['published' => true]) yozish o'rniga, bu holatlarga nom berib qo'yish mumkin β bu state ("holat") deb ataladi.
State β factory ichidagi metod. U definition() qiymatlarining bir qismini ustiga yozadi:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'published' => fake()->boolean(70),
];
}
// state: e'lon qilingan post
public function published(): static
{
return $this->state(fn (array $attributes) => [
'published' => true,
]);
}
// state: qoralama
public function draft(): static
{
return $this->state(fn (array $attributes) => [
'published' => false,
]);
}
}
Endi state'larni metod sifatida zanjirga qo'shasiz:
// 5 ta e'lon qilingan post:
Post::factory()->count(5)->published()->create();
// 3 ta qoralama:
Post::factory()->count(3)->draft()->create();
π state() ga beriladigan funksiya array $attributes (joriy qiymatlar) ni oladi va o'zgartiriladigan qismni qaytaradi. Bu qism definition() ustiga yoziladi (qolgani saqlanadi). Ya'ni published() faqat published ni o'zgartiradi, title va body factory'dan kelaveradi.
π‘ State'lar zanjirlanadi: bir nechta state'ni ketma-ket qo'ysangiz, ular birin-ketin qo'llanadi. State funksiyasi $attributesni olgani uchun, bir ustunni boshqasiga bog'lab ham bo'ladi:
public function published(): static
{
return $this->state(fn (array $attributes) => [
'published' => true,
'published_at' => now(), // e'lon qilingan bo'lsa, sanasi ham bor
]);
}
Quyidagi sof-PHP mantiq state'ning aslida nima qilishini ko'rsatadi β definition ustiga array_merge bilan state qo'yiladi:
$definition = ['title' => 'fake', 'published' => false]; // standart
$state = ['published' => true]; // published() state
$result = array_merge($definition, $state);
// natija: ['title' => 'fake', 'published' => true]
// title saqlandi, published ustiga yozildi
β
published(), draft(), unverified() β takror ishlatiladigan holatlarni nom bilan belgilang.
β Har joyda create(['published' => true]) takrorlash β state aynan shuni yo'qotish uchun bor.
Relation factory β bog'langan ma'lumot¶
Eng kuchli imkoniyat β factory'lar munosabatlar bilan ishlay oladi. Postning user_idsi bo'lishi kerak; user'ning postlari bo'lishi mumkin. Buni qo'lda bog'lamaysiz β factory hal qiladi.
has β "ota" tomondan (hasMany / hasOne)¶
Bitta user va unga bog'langan 5 ta post yaratamiz. has() β "egasi" tomonidan:
// 1 ta user + 5 ta post, har postda user_id avtomatik:
User::factory()
->has(Post::factory()->count(5))
->create();
user_idni hech qayerda yozmadik β has() uni o'zi to'ldiradi. Avval User yaratiladi, keyin uning idsi bilan 5 ta Post.
Munosabat nomi standartdan farq qilsa (yoki aniq aytmoqchi bo'lsangiz), ikkinchi argument bilan ko'rsatasiz:
User::factory()
->has(Post::factory()->count(5), 'posts') // 'posts' β User modelidagi metod nomi
->create();
π has() ishlashi uchun User modelida posts() munosabati (hasMany) bo'lishi shart β buni 9-bobda yozgan edik. Factory shu munosabatga qarab user_idni qaysi ustunga qo'yishni biladi.
π‘ Qisqaroq sintaksis ham bor β hasPosts() (has + munosabat nomi bosh harf bilan):
for β "bola" tomondan (belongsTo)¶
Teskari yo'l: postdan boshlab, egasini tayinlash. for() β belongsTo tomonidan:
// 3 ta post, hammasi bitta YANGI userga tegishli:
Post::factory()
->count(3)
->for(User::factory())
->create();
for() ga mavjud modelni ham berasiz (yangisini yaratmasdan):
$user = User::factory()->create();
Post::factory()->count(3)->for($user)->create(); // shu user'ning postlari
π has va for farqi: has β "menda quyidagilar bor" (ota β bolalar, bitta-koplab tomon), for β "men quyidagiga tegishliman" (bola β ota, FK tomoni). Qaysi biridan boshlash qulayligiga qarab tanlaysiz.
π‘ for() qisqartmasi ham bor β forUser():
hasAttached β many-to-many (pivot bilan)¶
Postga taglarni bog'lash (belongsToMany) uchun hasAttached():
$tags = Tag::factory()->count(5)->create();
Post::factory()
->count(10)
->hasAttached($tags) // har postga shu taglar bog'lanadi
->create();
Pivot jadvaliga qo'shimcha ustun (mas. added_by) kerak bo'lsa, ikkinchi argumentda berasiz (9-bobdagi withPivot eslang):
π hasAttachedga factory ham berish mumkin β shunda taglar ham aynan shu yerda yaratiladi:
Ilg'or qulayliklar: sequence, recycle, afterCreating¶
Uchta foydali metod bilan factory yanada kuchayadi.
sequence β qatorlarga navbat bilan har xil qiymat berish. boolean(70) tasodifiy, lekin "aniq yarmi published, yarmi draft bo'lsin" desangiz:
Post::factory()
->count(4)
->sequence(
['published' => true],
['published' => false],
)
->create();
// 1-post: true, 2-post: false, 3-post: true, 4-post: false (aylanib takrorlanadi)
recycle β mavjud modellarni qayta ishlatish. Odatda har bir for(User::factory()) yangi user yaratadi. 50 ta post yaratayotganda 50 ta yangi user kerak emas β 5 tasini aylantirib ishlatamiz:
$users = User::factory()->count(5)->create();
Post::factory()
->count(50)
->recycle($users) // 50 post, lekin faqat shu 5 user orasida taqsimlanadi
->create();
afterCreating β qator yaratilgandan keyin qo'shimcha ish bajarish:
Post::factory()
->afterCreating(function (Post $post) {
// mas. har post yaratilgach, unga bitta izoh qo'shamiz
$post->comments()->create([
'author' => fake()->name(),
'body' => fake()->sentence(),
]);
})
->create();
π‘ afterCreating ko'pincha factory ichida β configure() metodida β joylashtiriladi, shunda har safar avtomatik ishlaydi. Hozircha "yaratilgandan keyin ish bajarish kerak bo'lsa, shu bor" deb eslab qo'ying.
Seeder + Factory: birga ishlatish¶
Endi ikkalasini birlashtiramiz β bobning asosiy maqsadi. DatabaseSeederning run() metodida factory'larni ishga solamiz:
<?php
namespace Database\Seeders;
use App\Models\Post;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// 1) Aniq admin foydalanuvchi (kirish uchun qulay):
$admin = User::factory()->create([
'name' => 'Oqil Imomnazarov',
'email' => 'admin@example.com',
]);
// 2) 8 ta tag:
$tags = Tag::factory()->count(8)->create();
// 3) 10 ta oddiy user, har birida 5 tadan post:
User::factory()
->count(10)
->has(Post::factory()->count(5))
->create();
// 4) Admin'ning 20 ta e'lon qilingan posti, har biriga 1-3 tag:
Post::factory()
->count(20)
->for($admin)
->published()
->create()
->each(function (Post $post) use ($tags) {
$post->tags()->attach(
$tags->random(rand(1, 3))->pluck('id')->all()
);
});
}
}
Ishga tushiramiz:
Bir buyruq bilan: 1 admin + 10 user + (10Γ5 + 20) = 70 post + 8 tag + minglab pivot bog'lanish. Hammasi soxta-real, ishlatishga tayyor.
Alohida seederlarga bo'lish¶
Loyiha o'ssa, hammasini bitta run()ga tiqishtirmaymiz β har model uchun alohida seeder yasaymiz:
UserSeeder:
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
public function run(): void
{
User::factory()->count(10)->create();
}
}
Keyin DatabaseSeeder faqat dirijyorlik qiladi β qaysi seederlar, qanday tartibda ishlashini call() bilan belgilaydi:
public function run(): void
{
$this->call([
TagSeeder::class,
UserSeeder::class,
PostSeeder::class,
]);
}
π Tartib muhim! PostSeeder user_id ishlatadi, demak undan oldin UserSeeder ishlashi shart β aks holda postlar bog'lanadigan user topilmaydi va FK xatosi chiqadi. call() ro'yxatdagi tartibni saqlaydi: yuqoridan pastga.
β
Har model β alohida seeder; DatabaseSeeder ularni to'g'ri tartibda chaqiradi.
β Hammasini bitta ulkan run()ga yozish β o'qish va qayta ishlatish qiyinlashadi.
migrate:fresh --seed β bir buyruqda toza boshlash¶
Ishlab chiqishda baza ko'pincha "iflos" bo'lib qoladi: sinov qatorlari, o'chirilgan-qo'shilgan ma'lumot. Eng tozasi β bazani noldan boshlash. migrate:fresh hamma jadvalni tashlab, migratsiyalarni qaytadan ishlatadi:
--seed bayrog'ini qo'shsangiz, migratsiyadan darrov keyin seederlar ham ishlaydi:
Bu β eng ko'p ishlatiladigan buyruqlardan biri. Bitta satr bilan: hamma jadval tozalanadi β qayta yaratiladi β namuna ma'lumot bilan to'ladi. Loyihaga har qaytganingizda toza, to'la baza.
π migrate:fresh va migrate:refresh farqi: fresh jadvallarni DROP qiladi (butunlay o'chiradi), keyin migratsiyalarni ishlatadi β tez va toza. refresh esa har migratsiyaning down() (orqaga qaytarish) metodini chaqiradi β sekinroq, lekin down() to'g'ri yozilmagan bo'lsa muammo bo'lishi mumkin. Ishlab chiqishda odatda fresh qulayroq.
β DIQQAT: migrate:fresh HAMMA ma'lumotni o'chiradi! Buni faqat ishlab chiqish (local) bazasida ishlating. Ishlab turgan (production) bazada hech qachon β foydalanuvchilarning butun ma'lumoti yo'qoladi. Laravel productionda bu buyruqdan oldin tasdiq so'raydi, lekin baribir ehtiyot bo'ling.
π‘ Faqat seederni qayta ishga tushirish (jadvallarga tegmasdan) kerak bo'lsa β yana php artisan db:seed. Lekin u mavjud qatorlarga qo'shadi, tozalamaydi β shuning uchun ko'pincha migrate:fresh --seed afzal.
Tinker β hamma narsani jonli sinash¶
8-bobda Tinker bilan tanishdik. Bu bobda u ayniqsa qo'l keladi: factory'lar bazaga yozadi, demak natijani darrov ko'rib bo'ladi. Tinker β Laravel'ning interaktiv konsoli (REPL): terminalda PHP yozasiz, javobni shu zahoti olasiz.
Ochilgach, factory'larni jonli sinaymiz (> β Tinker chaqiruvi):
> Post::factory()->make()
= App\Models\Post {#... title: "Qui dolorem ipsum", published: true, ...}
> Post::factory()->count(3)->create()
= Illuminate\Database\Eloquent\Collection {#... all: [ ... 3 ta Post ... ]}
> Post::count()
= 3
> User::factory()->has(Post::factory()->count(2))->create()
= App\Models\User {#... id: 1, ...}
> User::find(1)->posts->count()
= 2
> Post::factory()->published()->create()->published
= true
π make() natijani darrov ko'rsatadi lekin bazaga yozmaydi β Tinker'da factory'ni "sinab ko'rish" uchun ideal: qanday qiymatlar chiqishini ko'rasiz, baza iflosanmaydi. Yoqsa β create() ga o'tasiz.
π‘ Tinker β factory yozayotganda eng tez tekshiruv vositasi. definition()ni o'zgartirdingizmi β Tinker'ni qayta ochib (exit, keyin php artisan tinker), Post::factory()->make() deb darrov ko'rasiz. Brauzer, route, controller kerak emas.
π Tinker kodni xotirada ushlaydi: yangi factory metodini qo'shsangiz, ochiq Tinker uni bilmaydi β chiqib (Ctrl+D yoki exit) qayta kiring.
Test uchun factory β keyingi bobga ko'prik¶
Factory'ning eng katta foydasi seederda emas β testda. 22-bobda to'liq ko'ramiz, hozir esa "nega muhimligini" his qilib qo'yamiz.
Tasavvur qiling: "postlar ro'yxati sahifasi faqat e'lon qilingan postlarni ko'rsatadimi?" degan testni yozyapsiz. Test uchun baza holatini aniq tayyorlash kerak: 3 ta published, 2 ta draft. Factory buni bir-ikki qatorda qiladi:
<?php
use App\Models\Post;
use App\Models\User;
it('faqat published postlarni qaytaradi', function () {
Post::factory()->count(3)->published()->create();
Post::factory()->count(2)->draft()->create();
$response = $this->get('/posts');
$response->assertStatus(200);
expect(Post::where('published', true)->count())->toBe(3);
});
Yoki "user o'z postlarini ko'ra oladimi?":
it('user oz postlarini kora oladi', function () {
$user = User::factory()->has(Post::factory()->count(4))->create();
expect($user->posts)->toHaveCount(4);
});
E'tibor bering β bu yerda state (published, draft) va relation (has) factory'lar naqadar qo'l kelyapti: testning maqsadini bir qarashda o'qib bo'ladi. Aynan shuning uchun factory'larni puxta yozish β yaxshi test yozishning poydevori.
π Har test odatda toza bazada boshlanadi (Laravel buni avtomatik qiladi β RefreshDatabase trait orqali, 22-bobda). Factory esa har testda kerakli ma'lumotni qaytadan tikadi. Shuning uchun factory test bilan ajralmas juftlik.
π‘ Qoida: modelni yaratganingizda factory'sini ham darrov yozing va asosiy state'larini belgilang. Bu seederda qulaylik, testda esa zarurat bo'ladi β keyin pushaymon bo'lmaysiz.
Yakuniy ko'rinish: to'liq factory¶
Bobni jamlasak, real loyihadagi PostFactory taxminan shunday bo'ladi:
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
public function definition(): array
{
return [
'user_id' => User::factory(), // bog'liq user (yo'q bo'lsa yaratiladi)
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'published' => fake()->boolean(70),
];
}
public function published(): static
{
return $this->state(fn (array $attributes) => [
'published' => true,
]);
}
public function draft(): static
{
return $this->state(fn (array $attributes) => [
'published' => false,
]);
}
}
π 'user_id' => User::factory() β qiziq nuqta: agar postni for()siz yaratasangiz, factory user_id uchun yangi user'ni o'zi yaratadi. Ya'ni Post::factory()->create() deb yozsangiz ham, postning egasi bo'ladi β hech qachon "egasiz" post qolmaydi. for() bersangiz esa, bu standart o'sha bilan almashadi.
Bu factory bilan endi: bitta post (Post::factory()->create()), yuzta post, faqat published'lar, faqat draft'lar, ma'lum userning postlari, taglar bilan β hammasini bir-ikki qatorda yasaysiz. Seeder bazani to'ldiradi, test bazani tayyorlaydi, Tinker hammasini sinaydi.
π Keyingi bobda (12) validatsiyani β foydalanuvchidan kelgan ma'lumotni tekshirishni β o'rganamiz. Hozirgacha ma'lumotni o'zimiz (factory orqali) yaratdik; endi tashqaridan kelgan ma'lumotni ishonchli qilishni ko'ramiz.
11-bob mashqlari¶
Quyidagi mashqlarni 9-10-boblardagi blog loyihasida (User, Post, Comment, Tag modellari bilan) bajaring. Har mashqdan keyin natijani Tinker'da (php artisan tinker) yoki php artisan db orqali tekshiring. Yechim berilmagan β har birini o'zingiz yozib, natijani ko'ring.
php artisan make:factory PostFactorybilanPostuchun factory yarating.definition()ichidatitle,bodyustunlarinifake()->sentence()vafake()->paragraphs(3, true)bilan to'ldiring.Postmodelidause HasFactoryborligini tekshiring. Bo'lmasa qo'shing vaPost::factory()->make()Tinker'da ishlashini tasdiqlang.- Tinker'da
Post::factory()->make()chaqiring β natija bazaga yozilmasdan faqat ekranda ko'rinishini,idnullekanini ko'ring. Post::factory()->create()bilan bitta post yarating vaPost::count()orqali bazada haqiqatan paydo bo'lganini tekshiring.Post::factory()->count(10)->create()bilan 10 ta post yarating. Har biriningtitlei boshqacha ekaniniPost::pluck('title')bilan tasdiqlang.Post::factory()->create(['title' => 'Maxsus sarlavha'])bilan sarlavhasi aniq, qolgani factory'dan bo'lgan post yarating βtitlesiz bergan,bodyfaker'dan ekanini ko'ring.PostFactorygapublishedustuninifake()->boolean(70)bilan qo'shing. 20 ta post yaratib, nechtasipublished = truechiqqaniniPost::where('published', true)->count()bilan sanang.PostFactorygapublished()vadraft()state metodlarini yozing ($this->state(...)).Post::factory()->published()->create()->publishedtrueqaytarishini tekshiring.Post::factory()->count(5)->draft()->create()bilan 5 ta qoralama yarating; hammasidapublished = falseekanini tasdiqlang.Useruchun factory'ni tekshiring (yangi loyihadaUserFactorybor).User::factory()->count(3)->create()bilan 3 ta user yarating.User::factory()->has(Post::factory()->count(5))->create()bilan bitta user va unga bog'langan 5 ta post yarating.User::find(1)->posts->count()5qaytarishini ko'ring.- Qisqa sintaksis
User::factory()->hasPosts(5)->create()bilan 11-mashqning natijasini takrorlang. Post::factory()->count(3)->for(User::factory())->create()bilan 3 ta postni bitta yangi userga bog'lang. Uchala postninguser_idsi bir xil ekanini tekshiring.- Avval
$user = User::factory()->create(), keyinPost::factory()->count(4)->for($user)->create()qiling β postlar mavjud userga bog'langanini (yangi user yaratilmaganini) tasdiqlang. Tag::factory()->count(5)->create()bilan 5 ta tag yarating (TagFactorydaname => fake()->unique()->word()). KeyinPost::factory()->hasAttached(Tag::factory()->count(2))->create()bilan taglari bor post yasang va$post->tags->count()ni tekshiring.Post::factory()->count(4)->sequence(['published' => true], ['published' => false])->create()bilan navbatma-navbat published/draft postlar yarating. 4 tadan 2 tasitrue, 2 tasifalseekanini ko'ring.make:seeder PostSeederbilan alohida seeder yarating,run()ichida 30 ta post yarating.php artisan db:seed --class=PostSeederbilan ishga tushiring.DatabaseSeederningrun()ichida$this->call([UserSeeder::class, PostSeeder::class])yozing. Tartib nega muhimligini (FK) izoh sifatida yozib qo'ying.php artisan migrate:fresh --seedni ishga tushiring. Bazadagi hamma jadval tozalanib, qayta yaratilib, seederlardan to'lganiniUser::count()vaPost::count()bilan tasdiqlang.- Bitta test yozing (
tests/Feature/PostTest.php):Post::factory()->count(3)->published()->create()vaPost::factory()->count(2)->draft()->create()qilib,Post::where('published', true)->count()aynan3ekaniniexpect(...)->toBe(3)bilan tekshiring.php artisan testbilan ishlatib ko'ring.