Seeding Table With Relationships In Laravel 8

Laravel provides the feature to seed your database with dummy data that can be used while testing.

Having a single seeder class for single/simple models is very easy. But in the real-world application, the models might be associated with each other in different Relationships. 

For database seeding, Laravel uses the fzaninotto/faker seeder package. 

All the seed classes are stored in the database/seeders directory. We have a DatabaseSeeder class defined as default. Using the call method from this class you can run other seed classes which allows you to control the seeding order.

So, how can we seed tables with relationships in Laravel 8? Let’s learn how to define relationships on the Eloquent model and use Laravel’s model factory to seed the database.

 

Install Laravel

We will install a fresh Laravel 8 (we are working with 8.26.1 version) project to use database seeding with relationships. We will create relationships between tables and then insert the dummy data. 

To create a new Laravel project, run the below command:

composer create-project laravel/laravel seeder-demo --prefer-dist

Once the installation is completed, open the project in your favorite code editor.
 

MySql Database Configuration

Open the  .env file and update the database configuration.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=seeder-demo
DB_USERNAME=root
DB_PASSWORD=password

 

Tables Creation

Laravel comes with few default migrations. So let's run them first by executing the below command:

php artisan migrate

laravel 8 seeding table with relationships

As you can see in the image, we have a users table created after running the above command. So we will create the rest of the tables: posts and comments.

php artisan make:migration create_posts_table
php artisan make:migration create_comments_table

how to seed tables having relationships in laravel 8

Now, we need to update our migration files to define the table columns. We don’t need to make any changes to the migration file of the users table as it has columns already defined.
 

public function up()
{
   Schema::create('users', function (Blueprint $table) {
       $table->id();
       $table->string('name');
       $table->string('email')->unique();
       $table->timestamp('email_verified_at')->nullable();
       $table->string('password');
       $table->rememberToken();
       $table->timestamps();
   });
}

Open the migration file of the posts table and update it with the below code:

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->unsignedBigInteger('user_id');
        $table->foreign('user_id')->references('id')->on('users');
        $table->timestamps();
    });
}

Open the migration file of the comments table and update it with the below code:

public function up()
{
    Schema::create('comments', function (Blueprint $table) {
        $table->id();
        $table->string('comment');
        $table->unsignedBigInteger('post_id');
        $table->foreign('post_id')->references('id')->on('posts');
        $table->unsignedBigInteger('user_id');
        $table->foreign('user_id')->references('id')->on('users');
        $table->timestamps();
    });
}

Run the migrations again to create new tables with defined fields.

php artisan migrate

laravel 8 tables seeding

 

Model, Controller, Seeder Creation

We have a User.php already created, so we will create an only controller and seeder class for the User model, and for Post, Comment models we have to create all three classes.

For User Model:

php artisan make:Controller UserController
php artisan make:seeder UserSeeder

seeding table with relationships in laravel 8

You can generate all these three classes using the single command as well.

For Post Model:

php artisan make:model Post -cs

For Comment Model:

php artisan make:model Comment -cs

how to seed tables having relationships in laravel 8

Factory Class Creation

We will create a factory class for Post and Comment models only as we have a User factory class already available.

php artisan make:factory PostFactory --model="App\\Post"
php artisan make:factory CommentFactory --model="App\\Comment"

laravel 8 seeding table with relationships

Models Relationship

We are going to define a relationship between three models: User, Post, and Comment. Consider an application in which:

  • A User has multiple Post(s) > A Post belongs to a User
  • A Post has multiple Comment(s) > A Comment belongs to a Post

seeding table with relationships in laravel 8

Add below code in respective model files.

User Model:

/**
* Get the posts for the user.
*/
public function posts()
{
    return $this->hasMany(Post::class);
}


Post Model:

/**
* Get the user that owns the post.
*/
public function user()
{
    return $this->belongsTo(User::class);
}
 
/**
* Get the comments for the blog post.
*/
public function comments()
{
    return $this->hasMany(Comment::class);
}


Comment Model:

/**
* Get the post that owns the comment.
*/
public function post(){
    return $this->belongsTo(Post::class);
}

Now we have a relationship set up between these three models. Let’s update our factory class to define a dummy data schema.

 

Update Factory Classes

Add below code in the respective model’s factory class files.

PostFactory

public function definition()
{
    return [
        'title'   => $this->faker->sentence(5),
        'user_id' => User::factory(1)->create()->first(),
    ];
}

CommentFactory

public function definition()
{
    return [
        'comment' => $this->faker->paragraph(2),
        'user_id' => User::factory(1)->create()->first(),
        'post_id' => Post::factory(1)->create()->first(),
    ];
}

 

Update Seeder Classes

Update the seeder classes with the below content.

UserSeeder

public function run()
{
    User::factory(5)->create();
}

PostSeeder

public function run()
{
    Post::factory(10)->create();
}

CommentSeeder

public function run()
{
    Comment::factory(20)->create();
}

 

Run Seeder Classes

You can run the seeder class separately using the below commands:

php artisan db:seed --class=UserSeeder
php artisan db:seed --class=PostSeeder
php artisan db:seed --class=CommentSeeder

laravel 8 seeding table with relationships

As you can see above, all three seeders ran successfully and inserted the related data into three tables!! Wow…

But, we have a problem here… :) 

Problem:

At first glance, it seems working but if you check the database tables, you will find that extra records are being created than written in seeder classes.

 

Reason:

We have written code in factory classes so that they can be executed independently and to allow this we have written code to generate a new related entity model record each time.

Check below the code of PostFactory and CommentFactory classes

public function definition()
{
    return [
        'title'   => $this->faker->sentence(5),
        'user_id' => User::factory(1)->create()->first(),
    ];
}
public function definition()
{
    return [
        'comment' => $this->faker->paragraph(2),
        'user_id' => User::factory(1)->create()->first(),
        'post_id' => Post::factory(1)->create()->first(),
    ];
}
  • Each time when a single post is created, it’s also creating a new record of a User in order to map with the user_id field.
  • Each time when a single comment is created, it’s also creating new records of a User and a Post in order to map with user_id and post_id fields.

 

Solution:

As all three models are related to each other, their seeders should not be executed separately. They should be executed in a specific order.

Make changes in factory classes as below:

public function definition()
{
    return [
        'title'   => $this->faker->sentence(5),
        'user_id' => rand(1, User::count())
    ];
}
public function definition()
{
    return [
        'comment' => $this->faker->paragraph(2),
        'user_id' => rand(1, User::count()),
        'post_id' => rand(1, Post::count())
    ];
}

Run the below command to run fresh migration

php artisan migrate:fresh

how to seed tables having relationships in laravel 8

And run the seeder in a specific order (UserSeeder, PostSeeder, CommentSeeder)

seeding table with relationships in laravel 8

If you check in the database you can see we have the exact numbers of records inserted that we defined in factory classes:

  • 5 Users
  • 10 Posts
  • 20 Comments

 

Improvement:

The above solution is working as expected, but we have to take care of the order of execution when running the seeder commands. If you remember, in the starting section of this article we have written below lines

All the seed classes are stored in the database/seeders directory. We have a DatabaseSeeder class defined as default. Using the call method from this class you can run other seed classes which allows you to control the seeding order.

Open DatabaseSeeder class file and update with below code:

public function run()
{
    $this->call([
        UserSeeder::class,
        PostSeeder::class,
        CommentSeeder::class,
    ]);
}

After making the above changes run the below command again:

php artisan migrate:fresh

Now, we don’t need to run three seeders separately or manually maintain their order. We just need to run a single seed command:

php artisan db:seed

seeding table with relationships in laravel 8


As you can see, it's working and inserted the exact records we have defined!! 

I hope this article will help you to work with seeder having relationships defined between models in Laravel.