Search code examples
phplaravellaravel-seeding

Factory Seed pivot table in Laravel with many-to-many relationships


I'm completely new to factories and seeds, and until now I've been manually adding data to each project in Sequel Pro or Workbench an.

I'm fairly new to Laravel in general, but I think I've got the models setup correctly.

I've got a recipe that can have multiple ingredients, and the ingredients can belong to multiple recipes.

The pivot between the two has a quantity and unit column (with the latter being nullable).

Recipe.php

class Recipe extends Model
{
    public function user(){
    return $this->belongsTo('App\User');
    }

    public function ingredients(){
    return $this->belongsToMany('App\Ingredient');
    }

}

Ingredient.php

class Ingredient extends Model
{
    public function recipes(){
        return $this->belongsToMany('App\Recipe');
    }
}

IngredientFactory.php

$factory->define(App\Ingredient::class, function (Faker $faker) {
    return [
        'name'       => $faker->word,
        'created_at'  => Carbon::now()->format('Y-m-d H:i:s'),
        'updated_at'  => Carbon::now()->format('Y-m-d H:i:s'),
    ];
});

RecipeFactory.php

$factory->define(App\Recipe::class, function (Faker $faker) {
    $userIds = User::all()->pluck('id')->toArray();

    return [
        'title'       => $faker->text(30),
        'description' => $faker->text(200),
        'user_id'     => $faker->randomElement($userIds),
        'created_at'  => Carbon::now()->format('Y-m-d H:i:s'),
        'updated_at'  => Carbon::now()->format('Y-m-d H:i:s'),
    ];
});

IngredientTableSeeder

public function run()
{
    factory(App\Ingredient::class, 100)->create();
}

RecipeTableSeeder

public function run()
{
    factory(App\Recipe::class, 30)->create();
}

That all works fine, but I'm trying to work out how I can generate data for the pivot table.

I feel like it should be something like this:

RecipeIngredientsFactory.php

$factory->define(?::class, function (Faker $faker) {
    $recipeIds = Recipe::all()->pluck('id')->toArray();
    $ingredientIds = Ingredient::all()->pluck('id')->toArray();

    return [
        'recipe_id'     => $faker->randomElement($recipeIds),
        'ingredient_id' => $faker->randomElement($ingredientIds),        
    ];
});

But I'd be unsure what to put as the ? for the model::class?

I could be completely off base, but the logic seems right.

Please let me know in the comments if any more info is required.


Solution

  • Generally speaking, you are defining the mapping table that provides the link between a Recipe and its Ingredients. From a very simplistic view, you would not have a model for that link in a lot of many-many relationship because the relationship has no meaning other than the linkage. In this case, you might build a Recipe Seeder like this:

    public function run()
    { 
        $recipes = factory(App\Recipe::class, 30)->create();
        $ingredients = factory(App\Ingredient::class, 20)->create();
        $recipes->each(function (App\Recipe $r) use ($ingredients) {
            $r->ingredients()->attach(
                $ingredients->random(rand(5, 10))->pluck('id')->toArray(),
                ['grams' => 5]
            );
        });
    }
    

    This will generate both the recipes and ingredients independently, and then you are just relating them in a standard way. You could also generate them each in a different seeder, and have a third to do the joins.

    In your specific case, you are joining Recipes and Ingredients. The corresponding relationship would have its own attributes, such how many grams of an ingredient you need for the recipe. In this case, you may find a joining model useful. Have a look at https://laravel.com/docs/5.7/eloquent-relationships#many-to-many under the section Defining Custom Intermediate Table Models.