Laravel Model Factories

Laravel Model Factories provide a convenient way to populate your database with Dummy Data during development as well as provide a way to insert Records while running Tests.

You can create a Model Factory using artisan command make:factory. You can also pass the model using -m Flag to specify the Model which will be associated with this Factory.

php artisan make:factory PostFactory -m Post

However, you can also create a Factory as soon as you create a Model by specifying the f flag. So following command will create a Model along with Factory as well as Migration File.

php artisan make:modle Post -mf

The Factory contains a method named as definition. This should return an array with all the columns of Model and their values. All Factories have access to Faker which can be used to generate random Values. All Laravel Project have a UserFactory Class which is located inside /database/factories Folder.

Below we are specifying that our Post Model will have comment column which should be random paragraph and an approved column which will hold a boolean value.

    public function definition()
    {
        return [
            //
            'comment' => $this->faker->paragraph(),
            'approved' => $this->faker->boolean(80),
        ];
    }

So in order to generate Records using the above Model, we can try below command.

App\Models\Post::factory()->create()

This will insert 1 Row in our Post Model with comment and approved column populated as we specified in the definition() of the Factory.

Sometimes, we do not want to insert records into the DB but we only want an instance of the Model. In this scenario, we can use the make() instead of the create() and it will return an instance of the Post Model without persisting it into the Database.

App\Models\Post::factory()->make()

It is also possible to create multiple records in 1 statement. We just need to chain the count() and pass it the number of records we need.

App\Models\Post::factory()->count(5)->create()

This will create 5 different records into the Post Model.

Sometimes you need to create a record with specific value. In that case we need to override the value that we have specified in the Factory Class. It is possible to do so by passing an array to the create method.

App\Models\Post::factory()->create(['comment' => 'Our Specific Comment']);

This will insert 1 Record into the Post Model with the comment column having the value that we specified above and approved column value will still be generated by the Faker.

If we need to generate only comments which are approved, we can do so using

App\Models\Post::factory()->count(5)->create(['approved' => true]);

However a better way to do so is using Factory States. We will define a method called as approved_posts as below:

    public function approved_posts() 
    {
        return $this->state(function (array $attributes) {
            return [
                'approved' => true,
            ];
        });
    }

Here approved_posts is our state and within it we specify that we always want the approved column to be true. Now we can write the above command as

App\Models\Post::factory()->count(5)->approved_posts()->create();

Same way we can create another state called as unapproved_posts() and set approved to false within that state. We can also create multiple states and chain them together.

Suppose you want to create 10 Posts with value of approved column alternating between true and false so that there are 5 Approved Posts and 5 Unapproved Posts. We can use Sequences for such Scenario. We can define such a Sequence as below :

Post::factory()
    ->count(10)
    ->state(new Sequence(
        ['approved' => true],
        ['approved' => false],
    ))
    ->create();

You can even define a Sequence like below so that every 3rd record created will be un-approved.

Post::factory()
    ->count(10)
    ->state(new Sequence(
        ['approved' => true],
        ['approved' => true],
        ['approved' => false],
    ))
    ->create();

Right now our Post Model is a stand along Model with no Relationship. However, normally a Post Model will be related to User Model such that Post belongsTo User. In this case our Post Model will have a user_id column and we will need to specify the value of user_id column. We can simply change our definition like below:

        return [
            //
            'user_id' => User::factory(),
            'comment' => $this->faker->paragraph(),
            'approved' => $this->faker->boolean(80),
        ];

We can continue creating our Post like below:

App\Models\Post::factory()->create()

However, now a User will also be created before the Post is created based on the definition in the UserFactory. If we run the below command.

App\Models\Post::factory()->count(5)->create()

It will create 5 Users and 5 Posts with each Post belonging to a different User. This way we can populate multiple tables with a single command.

However, we may want to create 5 Posts for a single User. We can do so by creating a User and then overriding the user_id attribute.

$user = User::factory()->create();
$posts = Post::factory()
            ->count(5)
            ->create(['user_id' => $user->id]);

However, we can also use the for() to associated Post and User. It is much more readable.

$user = User::factory()->create();
$posts = Post::factory()
            ->count(5)
            ->for($user)
            ->create();

We can also create both User and Post in a single command

User::factory()
    ->has(Post::factory()->count(5))
    ->create();

In fact, we can use still use the States or Sequence for the Post Factory, we have learned before, like below:

User::factory()
    ->has(Post::factory()->approved_posts()->count(5))
    ->create();

Laravel also provide Magic Methods which we can use like below:

$user = User::factory()
            ->hasPosts(3)
            ->create();

This assumes that there is a posts method in User Model which has been used to define relationship between User and Post Model.

    public function posts() 
    {
        return $this->hasMany(\App\Models\Post::class);
    } 

In order to show how easy it is to populate the Database with Test Data, lets say we have another Model Comment such that Post has Many Comments. We can then use below command to populate the whole Database

User::factory()
  ->count(50)
  ->has(
    Post::factory()
      ->has(Comment::factory()->count(10))
      ->count(5)
  )
  ->create();

This single command will create 50 Users. Each of those Users will have 10 Posts each. And each of the Post will have 5 comments each. You can move this code to Seeder and then you can seed your database using the below command

php artisan migrate:fresh --seed

This will re-run all your migrations and then populate your database with dummy data which is perfect for development purposes.

Leave a Reply

Your email address will not be published. Required fields are marked *