こんにちは、かつコーチです。
前回はSeederの3つの実装方法を紹介し、大量データにはFactoryとの組み合わせが向いていると触れました。
今回はそのFactoryを詳しく掘り下げていきます。
「テストデータを100件作りたいけど、1件ずつ書くのは面倒」という悩みを、Factoryはきれいに解決してくれます。
Factoryとは?
Seederとの役割の違い
Factoryとは、モデルのダミーデータをルールに従って自動生成する仕組みです。
「Seederと何が違うの?」と最初は混乱しやすいポイントなので、役割を整理しておきます。
| Seeder | Factory | |
|---|---|---|
| 役割 | データを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.php の faker_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_id を 1 に固定してしまうと、テスト環境に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を使わないクエリの書き方