環境

PHP 8.1
Laravel 9.19

テーブル構造

最初にマイグレーションファイルを見ていきましょう。
usersはデフォルトのまま。

Schema::create('users', static 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();
});

usersテーブルと一対多で関連付けされているpostsテーブル。

Schema::create('posts', static function (Blueprint $table) {
$table->id();
$table->string('title', 255);
$table->text('body');
$table->foreignId('user_id');
$table->timestamps();
});

postsテーブルと多対多の関連付けされているtagsテーブル。

Schema::create('tags', static function (Blueprint $table) {
$table->id();
$table->string('slug', 100);
$table->string('name', 100);
$table->timestamps();
});

postsテーブルとtagsテーブルの中間テーブルになるpost_tag
中間テーブルにもメモ的なカラムがあります。

Schema::create('post_tag', static function (Blueprint $table) {
$table->id();
$table->foreignId('post_id');
$table->foreignId('tag_id');
$table->string('memo')->nullable();
});

こんな感じでそれぞれのテーブルにFactoryでデータを作成していきます。

モデル

Factoryを使用するにはモデルが必要です。
Factoryを使用するモデルにはHasFactoryをuseする必要があります。
makeコマンドで作成しているならデフォルトでuseされています。

またリレーションの設定もこの段階で完了しておく必要があります。

app/Models/User.php
use Illuminate\Database\Eloquent\Factories\HasFactory;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
app/Models/Post.php
class Post extends Model
{
use HasFactory
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
}
app/Models/Tag.php
class Tag extends Model
{
use HasFactory;
public function posts(): BelongsToMany
{
return $this->belongsToMany(Post::class);
}
}

ファクトリ

いよいよファクトリファイルを作りますが、ファクトリもそんなに悩むところはありません。

UserFactory.phpは基本デフォルトのままですが、created_at,updated_atを入力しないとコマンドを実行した日時になるので、もしランダムに入れたいときはdateTimeBetweenを使うといいかもしれないです。

database/factories/UserFactory.php
public function definition()
{
$date = $this->faker->dateTimeBetween('-1year');
return [
'name' => $this->faker->unique()->userName(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
'created_at' => $date,
'updated_at' => $date,
];
}

次はusersテーブルと関連を持っているpostsです。
user_idにUser::factory()を指定します。

database/factories/PostFactory.php
public function definition()
{
$date = $this->faker->dateTimeBetween('-1year');
return [
'title' => $this->faker->realText(random_int(30, 250)),
'body' => $this->faker->realText(random_int(200, 800)),
'user_id' => User::factory(),
'created_at' => $date,
'updated_at' => $date,
];
}

最後にtagsテーブル。中間テーブルはモデルと同様に必要ありません。

database/factories/TagFactory.php
public function definition()
{
return [
'slug' => $this->faker->unique()->slug(),
'name' => $this->faker->unique()->word(),
];
}

これで準備は完了です。

シーダファイルの作成

ここがらが本題です。
作成したファクトリでどのようなデータが作成されるか確認していきましょう。
DatabaseSeeder.phpにfactoryメソッドを記述していきます。

最初にPostモデルのfactoryを単独で実行してみます。

database/seeders/DatabaseSeeder.php
public function run()
{
Post::factory(10)->create();
}

初回はmigrate実行時にseedオプションを付与して実行します。

$ php artisan migrate:fresh --seed

すでにテーブルがある場合はdb:seedを実行します。

$ php artisan db:seed

ファクトリファイルでUser::factory()を指定したので、postsと同じ数だけusersにもレコードが作成され関連付けされたと思います。
ただこれだと同じ数のデータなので一対一の関係ですね。

一対多のデータ作成

一であるUserを先に作成します。

User::factory(3)
->hasPosts(3)
->create();

ただこれだとUserが持っているPostの数が固定になります。
ランダムな数で関連付けしたい場合はUserデータを作成した後、Postのfactoryでrecycleを実行します。

$users = User::factory(5)->create();
Post::factory(50)->recycle($users)->create();

これでpostsテーブルのuser_idカラムに、あらかじめ作成したusersのランダムなidが入り一対多のデータになります。

多対多のデータ作英

多対多はいろいろパターンがあるのですが、最初はPostレコードの数だけTagも作成する方法です。
ひとつのPostに対して3つずつTagを作るのは下記のようにします。

Post::factory(50)
->hasTags(3)
->create();

次にあらかじめTagを作って関連付けする方法です。

$tags = Tag::factory(3)->create();
Post::factory(50)
->hasAttached($tags)
->create();

中間テーブルのカラムに入れる場合。

Post::factory(50)
->hasAttached(
$tags,
fn () => ['memo' => fake()->realText(random_int(10, 30))]
)
->create();

関連付けるTagの数もランダムにしたい場合。

Post::factory(50)
->create()
->each(fn ($post) =>
$post
->tags()
->attach($tags->random(random_int(1, 3)))
);

最後に中間テーブルのカラムもランダムで入れたい場合。

Post::factory(50)
->create()
->each(fn ($post) =>
array_map(static fn ($value) =>
$post
->tags()
->attach(
$tags->random(),
['memo' => fake()->realText(random_int(30, 50))]
), range(1, random_int(1, 3)))
);

もっとスマートな方法がありそうだけどわかりませんでした。以上。