Uczymy się pisać seeder i factory dla rekordów zawierających relację. Kontynuacja lekcji poprzednich – do dzieła!
Ok, najpierw AddressFactory:
class AddressFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'city' => fake()->city(),
'street' => fake()->streetName()
];
}
}
Teraz musimy odwiedzić PersonSeeder i dokonać pewnej zmiany:
use App\Models\Person;
use App\Models\Address;
class PersonSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
Person::factory()
->count(5)
->male()
->create()
->each(function($person){
$person->address()->save(Address::factory()->make());
});
Person::factory()
->count(5)
->female()
->create()
->each(function($person){
$person->address()->save(Address::factory()->make());
});
Person::factory()
->count(5)
->kids()
->create()
->each(function($person){
$person->address()->save(Address::factory()->make());
});
Person::factory()
->count(5)
->adults()
->create()
->each(function($person){
$person->address()->save(Address::factory()->make());
});
}
}
Wyjaśnienie:
- male, female, kids i adults to stany z PersonFactory, które zrobiliśmy poprzednio
- count pokazuje fabryce ile ma seeder tego naprodukować
- create to takie coś, co tworzy (w pamięci) i zapisuje do bazy danych
- make to taka metoda, która tworzy, ale nie zapisuje do bazy danych od razu
- each chyba samo się tłumaczy
- w callbacku each przyjmowany jest person i z użyciem fabryki adresu i notacji tradycyjnej do zapisywania relacji dopisuje ten adres
Teraz wystarczy użyć komendy:
php artisan db:seed --class=PersonSeeder
Gdyby jeszcze ktoś potrzebował te stany, to tutaj mamy PersonFactory:
class PersonFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'firstName' => fake()->firstName(),
'lastName' => fake()->lastName(),
'age' => fake()->numberBetween(16, 60),
'created_at' => fake()->dateTimeBetween('-2 years'),
'updated_at' => fake()->dateTimeBetween('created_at', 'now')
];
}
public function adults()
{
return $this->state(function (array $attributes) {
return [
'age' => fake()->numberBetween(19, 60),
];
});
}
public function kids()
{
return $this->state(function (array $attributes) {
return [
'age' => fake()->numberBetween(14, 18),
];
});
}
public function male()
{
return $this->state(function (array $attributes) {
return [
'firstName' => fake()->firstName('male'),
'lastName' => fake()->lastName('male'),
];
});
}
public function female()
{
return $this->state(function (array $attributes) {
return [
'firstName' => fake()->firstName('female'),
'lastName' => fake()->lastName('female'),
];
});
}
}
Natomiast słyszałem, że jest jeszcze jeden sposób zapisywania relacji przy seedingu. Robi się go w Factory, nie w seederze. Przykład:
$factory->define(App\Contact::class, function (Faker\Generator $faker) {
return [
'company_id' => factory('App\ContactCompany')->create()->id,
'first_name' => $faker->firstName(),
'last_name' => $faker->lastName,
'phone1' => $faker->phoneNumber,
'phone2' => $faker->phoneNumber,
'email' => $faker->email,
'skype' => $faker->word,
'address' => $faker->address,
];
});
To jest stary Laravel, więc nie ręczę za to, Laravel mocno ewoluuje (na przykład w 11 nie ma już middleware w konstruktorze przez $this->middleware) i nie ręczę, że to zadziała.
Ale logika jest prosta – masz tutaj company_id, wywołujesz fabrykę na ContactCompany, robisz create (czyli z zapisem do bazy danych) i pobierasz id, aby relacja się zapisała.
Bo tak ta „magia” wygląda pod spodem – to są po prostu numery nazwamodelu_id odpowiadające primary key w tabeli tego modelu.