12

Recently I have been trying to seed my database using Laravel seeding through Model Factories and Faker.

For simple schemas, it is just a breeze to have it working :). However, I have encountered several problems when working with complex DB schemas which involve foreign keys and table relationships:

  • One to One
  • One to Many
  • Many to Many

...Like the one described in the link: Laravel 5.1 foreign keys in model factory.

In this topic, the official documentation suggests to run the database seeds like this:

public function run()
{
    factory(App\User::class, 50)->create()->each(function ($u) {
        $u->posts()->save(factory(App\Post::class)->make());
    });
}

... but there is one problem with this solution: when working with many DB tables and running many seeds (with many relations between them), it is common to create many unnecessary models using this methodology. For instance, if we had run the PostsTableSeeder.php before the one of the above example, all those posts would not have been linked to users, and would never be used in tests and development...

So searching for a way to handle this situation, I have come up to a functional solution that works for me and avoids the unnecessary creation of those 'orphan' models...

And I wanted to share it with everyone, so it is just explained in the answer :).

Cœur
  • 32,421
  • 21
  • 173
  • 232
andcl
  • 2,473
  • 3
  • 25
  • 49

2 Answers2

34

So here is my solution:

The example deals with:

  • Users & Profiles (for illustrating One to One relationships)
  • Users & Posts (for illustrating One to Many relationships)

    // ONE TO ONE relationship (with Users already created)
    $factory->define(App\Profile::class, function (Faker\Generator $faker) {
        return [
            'user_id' => $faker->unique()->numberBetween(1, App\User::count()),
            // Rest of attributes...
        ];
    });
    
    // ONE TO MANY relationship (with Users already created)
    $factory->define(App\Posts::class, function (Faker\Generator $faker) {
        $users = App\User::pluck('id')->toArray();
        return [
            'user_id' => $faker->randomElement($users),
            // Rest of attributes...
        ];
    });
    
andcl
  • 2,473
  • 3
  • 25
  • 49
  • 1
    I don't suppose you could suggest the best way to achieve the same for a Many to Many relationship? I have Users and Cases with a User_Cases table to resolve the many to many. I'm trying to write a factory to seed my db. – Kenilik Sep 10 '17 at 10:29
  • 1
    I get the **many to many** approach within seeders instead of with factories... In your seeder, you need to simply retrieve both models (*Users* and *Cases*) and loop through one of them assigning the other(s) in each case. – andcl Sep 11 '17 at 10:51
5

Here is a solution to make relationships that is way better than assigning random Users, especially if you need to send extra information to this model.

$factory->define(App\Post::class, function (Faker\Generator $faker) {
    $user = factory('App\Models\User')->create(['email' => 'email@test.com',]);
    // do your relationships here (...)
    return [
        'user_id' => $user->id,
        'title'   => $faker->sentence,
        'body'    => $faker->paragraph,
        ];
    }

And I saw another example with the use of anonymous function

$factory->define(App\Post::class, function (Faker\Generator $faker) {
    return [
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        },
        'title' => $faker->sentence,
        'body'  => $faker->paragraph,
    ];
}

Source: https://laracasts.com/series/laravel-from-scratch-2017/episodes/22

mega6382
  • 8,624
  • 16
  • 43
  • 65
a20121248
  • 51
  • 1
  • 5
  • The use of the anonymous function is the right way to do this here. If you don't put the creation of the user inside an anonymous function as shown in the second example, then a new User model will be created every time you call the factory create/make methods. If you pass in the `user_id` of an existing user to the factory method, it would still create a new user even though the post wouldn't be attached to it (it would be attached to the user_id you provided). By using an anonymous function as in the second example it will only create a new User when you don't provide a user_id to it. – scorgn May 07 '21 at 17:00