How do i write a Seeder with Factories in Laravel 8?

1.2k Views Asked by At

My Problem

I've recently started working with Laravel 8. I'm trying to generate test data using seeder and factories. But I always run into errors and the documentation of Laravel 8 is too poor for my case. I would be very grateful if someone can help me with my problem.

My Code

UserFactory

<?php

namespace Database\Factories;

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

class UserFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = User::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'firstname' => $this->faker->name(),
            'lastname' => $this->faker->name(),

            "birthdate" => $this->faker->date(),
            "birthplace" => $this->faker->city(),

            'email' => $this->faker->unique()->freeEmail(),
            'password' => $this->faker->password(),
        ];
    }

    /**
     * Indicate that the model's email address should be unverified.
     *
     * @return \Illuminate\Database\Eloquent\Factories\Factory
     */
    public function unverified()
    {
        return $this->state(function (array $attributes) {
            return [
                'email_verified_at' => null,
            ];
        });
    }
}

UserEmailFactory

<?php

namespace Database\Factories;

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

class UserEmailFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = UserEmail::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            "name" => $this->faker->words(1),
            "email" => $this->faker->unique()->freeEmail(),
        ];
    }
}

User Model

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    /**
     * Tablename
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * PrimaryKey
     *
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'firstname',
        'lastname',
        'birthdate',
        'birthplace',
        'taxnumber',
        'email',
        'password'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password'
    ];

    /**
     * Relation: One User has many UserEmails
     */
    public function useremails() {
        $this->hasMany(UserEmail::class);
    }
    
    /**
     * Relation: One User has many UserPasswords
     */
    public function userpasswords() {
        $this->hasMany(UserPassword::class);
    }
    
    /**
     * Relation: One User has many UserPhones
     */
    public function userphones() {
        $this->hasMany(UserPhone::class);
    }
}

UserEmail Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class UserEmail extends Model
{
    use HasFactory;

    /**
     * Tablename
     *
     * @var string
     */
    protected $table = 'user_emails';

    /**
     * PrimaryKey
     *
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'user_id',
        'name',
        'email'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
    ];


    /**
     * Relation: One UserEmail belongs to a User
     */
    public function user() {
        return $this->belongsTo(User::class);
    }
}

My Tests

UserSeeder Test 1

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

use App\Models\User;
use App\Models\UserEmail;
use App\Models\UserPassword;
use App\Models\UserPhone;

class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run() {
        for ($i = 0; $i < 100; $i++) {
            $user = User::factory()->create();
            
            $userEmail = UserEmail::factory()
                ->count(5)
                ->state(
                    function (array $attributes, User $user) {
                        return ['user_id' => $user->id];
                    }
                )
                ->create();
            $userPassword = UserPassword::factory()
                ->count(5)
                ->state(
                    function (array $attributes, User $user) {
                        return ['user_id' => $user->id];
                    }
                )
                ->create();
            $userPhone = UserPhone::factory()
                ->count(5)
                ->state(
                    function (array $attributes, User $user) {
                        return ['user_id' => $user->id];
                    }
                )
                ->create();
        }
    }
}
Database\Seeders\UserSeeder::Database\Seeders\{closure}(): Argument #2 ($user) must be of type App\Models\User, null given

UserSeeder Test 2

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

use App\Models\User;
use App\Models\UserEmail;
use App\Models\UserPassword;
use App\Models\UserPhone;

class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run() {
        $user = User::factory()
            ->has(
                UserEmail::factory()
                    ->count(5)
                    ->state(function (array $attributes, User $user) {
                        return ['user_id' => $user->id];
                    })
                    ->create()
            )
            ->has(
                UserPassword::factory()
                    ->count(5)
                    ->state(function (array $attributes, User $user) {
                        return ['user_id' => $user->id];
                    })
                    ->create()
            )
            ->has(
                UserPhone::factory()
                    ->count(5)
                    ->state(function (array $attributes, User $user) {
                        return ['user_id' => $user->id];
                    })
                    ->create()
            )            
            ->create();
    }
}
Database\Seeders\UserSeeder::Database\Seeders\{closure}(): Argument #2 ($user) must be of type App\Models\User, null given
1

There are 1 best solutions below

0
Shawn H On

(Note: I haven't tested the code below)

In your UserFactory, I'd define states with methods called emails(), phones(), and passwords(). Here's an example of emails():

 /**
 * Add emails to this user account
 *
 * @return \Illuminate\Database\Eloquent\Factories\Factory
 */
public function emails(int $count)
{
    return $this->afterCreating(function (User $user) use ($count) {
        for ($i = 0; $i < $count; $i++) {
            $user->useremails()->create([
                    'email' => $this->faker->safeEmail,
            ]);
        }

    });
}

Then, when you need to create the User, you can run:

public function run()
{
    \App\Models\User::factory()
                ->count(5)
                ->emails(5)
                ->create();
}

and it will create 5 users, with 5 email addresses each. As a bonus, in the emails() state method you can update the $user record so that the "email" column in the User table matches one of your UserEmail records.

If you wanted to create a User with 5 email address, and 2 phone numbers, you could call:

public function run()
{
    \App\Models\User::factory()
                ->count(5)
                ->emails(5)
                ->phones(2)
                ->create();
}