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.