【Laravel】Factoryでダミーデータを効率よく作る

laravelアイキャッチ Laravel

こんにちは、かつコーチです。

前回はSeederの3つの実装方法を紹介し、大量データにはFactoryとの組み合わせが向いていると触れました。

今回はそのFactoryを詳しく掘り下げていきます。

「テストデータを100件作りたいけど、1件ずつ書くのは面倒」という悩みを、Factoryはきれいに解決してくれます。

Factoryとは?

Seederとの役割の違い

Factoryとは、モデルのダミーデータをルールに従って自動生成する仕組みです。

「Seederと何が違うの?」と最初は混乱しやすいポイントなので、役割を整理しておきます。

SeederFactory
役割データをDBに投入する「実行役」データの「作り方」を定義する設計図
向いている用途実行の入り口・順序の管理ランダムなダミーデータの生成ルール
単体で使うか単体でも使う単体では使わず、Seederやテストから呼び出す

イメージとしては、Factoryが「どんなデータを作るかのレシピ」で、Seederが「そのレシピを使って実際に料理を作って並べる係」だと考えると分かりやすいです。

なぜFactoryが必要なのか

「N+1問題を確認したい」「ページネーションの動きを見たい」といった場面では、数件のデータでは状況を再現できません。

ユーザー100人分、投稿1,000件分といったボリュームのあるデータが必要になりますが、これを1件ずつ手で書くのは非現実的です。

Factoryを使えば、名前・メールアドレス・本文といったダミーデータのルールを一度定義するだけで、何百件でも一瞬で生成できます。

実装手順

手順1:Factoryクラスを作成する

make:factory コマンドでFactoryのひな形を作成します。

php artisan make:factory ArticleFactory --model=Article
<?php
// database/factories/ArticleFactory.php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class ArticleFactory extends Factory
{
    public function definition(): array
    {
        return [
            'title' => fake()->sentence(6),
            'body' => fake()->paragraphs(3, true),
            'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
        ];
    }
}

definition() メソッドの戻り値が、1件分のダミーデータの中身になります。

fake() ヘルパーは、内部で Faker というライブラリを呼び出しており、氏名・住所・文章など様々な種類のランダムなデータを生成できます。

手順2:モデルにHasFactoryトレイトをつける

Factoryを使うモデル側には、HasFactory トレイトが必要です。

make:model でモデルを作成した場合は最初から付いていますが、念のため確認しておきましょう。

<?php
// app/Models/Article.php

namespace App\Models;

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

class Article extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'body', 'published_at'];
}

手順3:データを生成する

Factoryを使ってデータを生成する方法はいくつかあります。

<?php

use App\Models\Article;

// 1件だけDBに保存する
Article::factory()->create();

// 10件まとめてDBに保存する
Article::factory()->count(10)->create();

// DBには保存せず、インスタンスだけ作る(テストで便利)
$article = Article::factory()->make();

// 一部の値だけ上書きする
Article::factory()->create([
    'title' => '公開予定の記事',
]);

create() はDBに保存し、make() はインスタンスを作るだけでDBには保存しません。

配列を渡せば、特定のカラムだけ固定値に上書きできるので、「タイトルだけは指定したい」というときに便利です。

よく使うFakerのメソッド

日本語データを生成する

fake() はデフォルトだと英語のダミーデータを生成しますが、日本語のデータを使いたい場合は config/app.phpfaker_locale を変更しておくと自然です。

<?php
// config/app.php

'faker_locale' => 'ja_JP',

これを設定しておくと、fake()->name() で日本人らしい氏名が、fake()->address() で日本の住所らしい文字列が生成されるようになります。

よく使うメソッド一覧

<?php

fake()->name();               // 氏名
fake()->email();              // メールアドレス
fake()->sentence();           // 短い文章
fake()->paragraph();          // 段落
fake()->numberBetween(1, 100); // 指定範囲の数値
fake()->boolean();            // true / false
fake()->dateTimeBetween('-1 month', 'now'); // 期間内の日時
fake()->randomElement(['下書き', '公開中', '非公開']); // 配列からランダムに1つ

randomElement() は、ステータスのように決まった選択肢からランダムに1つ選びたいときに特に重宝します。

つまずきやすいポイント:リレーション先のIDを固定値で書いてしまう

存在しないIDを参照してエラーになる

私が実際につまずいたのは、リレーション先のIDをFactory内に直接書いてしまったケースです。

❌ Before:関連するuser_idを固定値で書く

<?php
// database/factories/ArticleFactory.php

public function definition(): array
{
    return [
        'user_id' => 1, // 決め打ちで書いてしまう
        'title' => fake()->sentence(6),
        'body' => fake()->paragraphs(3, true),
    ];
}
SQLSTATE[23000]: Integrity constraint violation:
1452 Cannot add or update a child row: a foreign key constraint fails

user_id1 に固定してしまうと、テスト環境にID1のユーザーが存在しない場合、外部キー制約に違反してエラーになります。

さらに、記事を100件作っても全部同じユーザーの投稿になってしまい、データとしての多様性も失われます。

✅ After:関連モデルのFactoryを呼び出して自動生成する

<?php
// database/factories/ArticleFactory.php

use App\Models\User;

public function definition(): array
{
    return [
        'user_id' => User::factory(),
        'title' => fake()->sentence(6),
        'body' => fake()->paragraphs(3, true),
    ];
}

User::factory() を渡すと、Article を生成するたびに新しいユーザーが自動で作成され、そのIDが user_id に紐づきます。

「関連するモデルのIDは決め打ちせず、相手側のFactoryを呼び出す」というのが多くの人が最初につまずくポイントなので、ぜひ覚えておいてください。

応用:SeederとFactoryを組み合わせて多対多データを作る

belongsToManyとFactoryの合わせ技

前々回扱った belongsToMany(多対多)のデータも、Factoryと has() メソッドを組み合わせると効率よく作れます。

<?php
// database/seeders/ArticleSeeder.php

namespace Database\Seeders;

use App\Models\Article;
use App\Models\Tag;
use Illuminate\Database\Seeder;

class ArticleSeeder extends Seeder
{
    public function run(): void
    {
        // タグを5件作っておく
        $tags = Tag::factory()->count(5)->create();

        // 記事を20件作り、それぞれにランダムなタグを2〜3個紐づける
        Article::factory()
            ->count(20)
            ->create()
            ->each(function (Article $article) use ($tags) {
                $article->tags()->attach(
                    $tags->random(rand(2, 3))->pluck('id')
                );
            });
    }
}

each() の中で attach() を呼び出すことで、生成した20件の記事それぞれに、ランダムに選んだ2〜3個のタグを紐づけられます。

$tags->random(rand(2, 3)) の部分で、コレクションからランダムに複数件取り出しています。

こうしてSeederとFactoryを組み合わせることで、リレーションを含んだ現実的なテストデータをコマンド一発で用意できるようになります。

まとめ

この記事のポイント

  • Factoryはダミーデータの「作り方」を定義する設計図で、Seederから呼び出して使う
  • create() はDB保存、make() はインスタンス作成のみという違いがある
  • fake() ヘルパーで氏名・文章・日時など多様なダミーデータを生成できる
  • リレーション先のIDは決め打ちせず、相手側の Factory を呼び出す
  • SeederとFactoryを組み合わせれば、多対多を含む現実的なテストデータも一括生成できる

次に読むべき記事

テストデータが揃ったところで、次はEloquentを使わない生のクエリの書き方であるQuery Builderを見ていきましょう。

→ 次の記事:Query Builderの基本:Eloquentを使わないクエリの書き方

タイトルとURLをコピーしました