23

I'm trying to add a relation to a factory model to do some database seeding as follows - note I'm trying to add 2 posts to each user

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

But its throwing the following error

Argument 1 passed to Illuminate\Database\Eloquent\Relations\HasOneOrMany::s  
ave() must be an instance of Illuminate\Database\Eloquent\Model, instance 
of Illuminate\Database\Eloquent\Collection given

I think its something to do with saving a collection. If re-write the code by calling each factory model for the post separately it seems to work. Obviously this isn't very elegant because if I want to persist 10 or post to each user then I'm having to decalare 10 or lines unless I use some kind of for loop.

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

* UPDATED *

Is there any way to nest the model factory a 3rd level deep?

public function run()
{
   factory(App\User::class, 50)
       ->create()
       ->each(function($u) {
           $u->posts()->saveMany(factory(App\Post::class, 2)
                    ->make()
                    ->each(function($p){
                          $p->comments()->save(factory(App\Comment::class)->make());
          }));
   });
}
Christian
  • 27,509
  • 17
  • 111
  • 155
adam78
  • 9,668
  • 24
  • 96
  • 207
  • 4
    Models have a saveMany() method that accepts Collection objects. Try `$u->posts()->saveMany(factory(App\Post::class, 2)->make());`. However, bear in mind this will still generate 2 save queries in the background, it will not persist with one query. – Yasen Slavov Sep 12 '15 at 16:25
  • 4
    @Yasen: The saveMany() method works. Is there any way to nest the model factory a 3rd level deeper? See updated post as to what I mean. – adam78 Sep 12 '15 at 18:07
  • 1
    Have you found a solution of more than 2 levels nesting? – Alex Lomia Apr 21 '16 at 20:52
  • https://laravel.com/docs/8.x/database-testing – Ostap Brehin Jun 30 '21 at 11:39

7 Answers7

21

Since Laravel 5.6 there is a callback functions afterCreating & afterMaking allowing you to add relations directly after creation/make:

$factory->afterCreating(App\User::class, function ($user, $faker) {
    $user->saveMany(factory(App\Post::class, 10)->make());
});

$factory->afterMaking(App\Post::class, function ($post, $faker) {
    $post->save(factory(App\Comment::class)->make());
});

Now

factory(App\User::class, 50)->create()

will give you 50 users with each having 10 posts and each post has one comment.

Adam
  • 25,960
  • 22
  • 158
  • 247
13

Try this. It worked for me:

factory(\App\Models\Auth\User::class, 300)->create()->each(function ($s) {
                    $s->spots()->saveMany(factory(\App\Models\Spots\Parking::class, 2)->create()->each(function ($u) {
                            $u->pricing()->save(factory(\App\Models\Payment\Pricing::class)->make());
                    }));
                    $s->info()->save(factory(\App\Models\User\Data::class)->make());
            });
prog_24
  • 771
  • 1
  • 12
  • 26
8

For a 3rd level nested relationship, if you want to create the data with the proper corresponding foreign keys, you can loop through all the results from creating the posts, like so:

factory(App\User::class, 50)->create()->each(function($u) {
    $u->posts()
        ->saveMany( factory(App\Post::class, 2)->make() )
        ->each(function($p){
            $p->comments()->save(factory(App\Comment::class)->make());
        });
});
  • 1
    Solved for me, and I've been googling around for half an hour trying to find something convincing. – OGHaza Feb 26 '19 at 09:19
6

To answer the initial problem / error message:

The problem indeed has to do with saving the data. Your code:

$u->posts()->save(factory(App\Post::class, 2)->make());

... should be changed to

$u->posts()->saveMany(factory(App\Post::class, 2)->make());

From the laravel docs:

You may use the createMany method to create multiple related models:

$user->posts()->createMany( factory(App\Post::class, 3)->make()->toArray() );

That means:

  • when only creating one model with the factory, you should use save() or create()
  • when creating multiple models with the factory, you should use saveMany() or createMany()
askuri
  • 345
  • 3
  • 10
2
$factory->define(User::class, function (Faker $faker) {
return [
    'name' => $faker->name,
    'email' => $faker->unique()->safeEmail,
    'email_verified_at' => now(),
    'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // 
password
    'remember_token' => Str::random(10),
];
});

$factory->define(Post::class, function ($faker) use ($factory) {
return [
    'title' => $faker->sentence(3),
    'content' => $faker->paragraph(5),
    'user_id' => User::pluck('id')[$faker->numberBetween(1,User::count()-1)]
];
});
1

Let me, explain How to add multi level of relationship of call factory in Laravel 9 By concept of new school and old school. The new school is:

 \App\Models\Author::factory()
        ->has(
            \App\Models\Article::factory(1)
                ->has(
                    \App\Models\Comment::factory(9)
                        ->has(
                            \App\Models\Reply::factory(2)
                        )

                ))->create();`enter code here`

That's for Laravel 9 . There's anthor way call Magic method. let me explain that:

 \App\Models\Author::factory()->hasArticles(1)->hasComments(9)->hasReplies(2)->create();

this hasArticles() is the name of method of relationship in parent model should convert the name with has. for example: comments() convert to hasComments().

Now lets explain old school that's still prefect in some cases and still works good with Laravel 9.

        \App\Models\Author::factory(1)->create()
        ->each(function($a) {
            $a->articles()->saveMany( \App\Models\Article::factory(2)->create() )
            ->each(function($p){
                $p->comments()->saveMany(\App\Models\Comment::factory(5))->create()
                    ->each(function($r){
                    $r->replies()->saveMany(\App\Models\Reply::factory(5))->create();
                });
            });
    });

of course you can replace method saveMany() by save() as your relationship you have. also you can replace method create() by make() if you want to doesn't save in database for test purposes.

Enjoy.

-1

In version laravel 9, use like this

<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
 */
class PostFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition()
    {
        return [
            'user_id' => User::factory(),
            'title' => fake()->paragraph()
        ];
    }
}```
Mahedi Hasan Durjoy
  • 1,031
  • 13
  • 17