phplaraveleloquent

Eloquent - Factory of n elements belonging to another with count


So, as an exercise in ORM modeling with laravel, I'm trying to implement this model:

Food Ordering System

It's a bit tricky for me to write the factories and seeders for the Restaurant - OpeningHours relationship.

How do I make a factory for opening hours that increments the "Day" attribute for each OpeningHour belonging to One restaurant?

By that, I mean there must be 7 OpeningHours for each restaurant, and each should have a "Day" attribute from 1 to 7.

Any Ideas?

here's the code for the OpeningHours Factory:

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\OpeningHours>
 */
class OpeningHoursFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            "day" => fake()->numberBetween(0,6),
            "from_hour" => 0,
            "from_minute" => 0,
            "to_hour" => 23,
            "to_minute" => 59,
        ];
    }
}

Here's the code for the Restaurant Factory

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Restaurant>
 */
class RestaurantFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            "short_description" => fake()->text(127),
            "description" => fake()->text(254),
            "image" => fake()->image(),
            "is_active" => fake()->boolean()
        ];
    }
}

And here's the Restaurant Seeder


<?php

namespace Database\Seeders;

use App\Models\OpeningHours;
use App\Models\Restaurant;
use Database\Factories\RestaurantFactory;
use Illuminate\Database\Seeder;

class RestaurantSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        Restaurant::factory()->has(OpeningHours::factory()->count(7) )->count(10)->create();
    }
}

Solution

  • I will try to write a seeder for you, it may not exactly be what you want, but it is based on what I understood.

    Have in mind that seeders allows you to do anything you want, you can even run code that has nothing to do with factories. As the name implies, seeder is for seeding data (usually known or completely random from factories) into your database, so here you have the choice of full control (what I am going to do) versus complete randomness.

    I will use Laravel 12.x:

    <?php
    
    namespace Database\Seeders;
    
    use App\Models\OpeningHours;
    use App\Models\Restaurant;
    use Database\Factories\RestaurantFactory;
    use Illuminate\Database\Eloquent\Collection;
    use Illuminate\Database\Eloquent\Factories\Sequence;
    use Illuminate\Database\Seeder;
    
    class RestaurantSeeder extends Seeder
    {
        public function run(): void
        {
            Restaurant::factory()
                ->count(10)
                ->create()
                ->each(function (Restaurant $restaurant) {
                    /** @var Collection<OpeningHours> $hours */
                    $hours = OpeningHours::factory()
                        ->count(7)
                        ->sequence(
                            fn (Sequence $sequence) => ['day' => $sequence->index + 1]
                        )
                        ->create();
    
                    $restaurant->openingHours()->saveMany($hours);
                });
        }
    }
    

    I do not know the relationships you have created (it should be HasMany for Restaurant to OpeningHours), then you can use saveMany for openingHours relationship.

    This is what is happening:

    1. We start a Restaurant factory, creating 10 restaurants.
    2. Because the factory will return a Collection, we can iterate each model, and we do something with each one (associate hours).
    3. For each Restaurant model, we create 7 new OpeningHours (the name of the model should be OpeningHour), and each time one is created, we set the day to be the current index (starts from 0) + 1, so we go from 1 to 7.
    4. After the hours are created, we associate those to the current restaurant with saveMany using the relationship.