Laravel

Laravel Seederの使い方|Factory連携と本番投入してよいデータの線引き

Laravel Seederの使い方|Factory連携と本番投入してよいデータの線引き

Seederは、データベースへ初期データを流し込むためにLaravelが用意しているクラスです。手順そのものはmake:seederとdb:seedの2コマンドで終わりますが、受託案件で困るのはそこから先にあります。開発用のダミーデータと本番へ持っていくマスタデータが同じDatabaseSeederにぶら下がったまま運用に入ると、本番で流した瞬間にテストユーザーが混ざる、あるいは二度流して同じマスタが重複する、といった事故の原因です。この記事では2026年8月時点のLaravel 13系(laravel/framework v13.29.0系)の配布物と公式ドキュメントを実測し、Seederの書き方とFactoryとの連携、そして本番データベースへ流してよいデータの線引きまでを一本で通します。マイグレーションそのものの基礎はLaravelにおけるマイグレーションの基本とその重要性に譲り、ここでは入れるデータ側の設計に絞ります。

まとめ:Seederの構成・Factory連携・本番投入の判断

  • make:seederで生成されるのはrunメソッドだけを持つクラスで、database/seeders配下に置かれます。投入処理はクエリビルダでもEloquentでも書けます。
  • DatabaseSeederのcallで子シーダーを列挙し、依存関係の順に並べます。二重呼び出しを避けたいときはcallOnceを使います。
  • 13系の配布版DatabaseSeederはWithoutModelEventsを最初からuseしています。オブザーバやイベントリスナーは既定で発火しません。
  • 大量のレコードはFactoryへ寄せます。stateで差分、sequenceで交互、hasとforでリレーション、recycleで共通の親を使い回します。
  • 作り直しはmigrate:fresh、戻して再実行はmigrate:refreshに--seedを付けます。1本だけ流すならdb:seedの--classを使います。
  • 本番へ流すのはマスタデータだけに限り、そのSeederは何度実行しても同じ結果になるように書きます。ダミーデータ側とはクラスを分けます。
  • DB::prohibitDestructiveCommandsはmigrate:fresh系を止めますがdb:seedは対象外です。誤爆の防御は自分で設計します。

Seederはdatabaseディレクトリ配下の投入専用クラスとして動く

まず置き場所と最小構成を押さえることが出発点です。Seederはマイグレーションと違って実行履歴を持たず、走らせた回数だけ処理が走ります。この性質が後の本番運用の話に直結するので、最初に意識しておくと設計が楽になります。

make:seederが生成するのはrunメソッドだけの薄いクラス

生成コマンドは1本です。出力先はdatabase/seedersで、名前空間はDatabase\Seedersになります。

php artisan make:seeder ProductCategorySeeder

生成されるクラスが持つメソッドはrunのみです。この中身に決まりはなく、クエリビルダで直接insertしても、Eloquentモデル経由で書いても構いません。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class ProductCategorySeeder extends Seeder
{
    public function run(): void
    {
        DB::table('product_categories')->insert([
            ['code' => 'book', 'name' => '書籍'],
            ['code' => 'food', 'name' => '食品'],
        ]);
    }
}

シーディング中はマスアサインメント保護が自動で無効になります。モデル側の$fillableに載っていない属性でも投入できるため、主キーを固定したいマスタデータの流し込みで引っかかりません。裏を返すと、モデルの防御が効かない状態で書き込んでいるということでもあります。外部から受け取った値をそのままSeederへ通す設計は避けてください。なおrunの引数に型宣言を書けば、サービスコンテナからそのまま解決されて渡ります。

DatabaseSeederのcallで子シーダーの実行順序を組み立てる

db:seedが既定で呼ぶのはDatabase\Seeders\DatabaseSeederの1本だけです。ここからcallで子を呼び出す形にして、1クラスが肥大化しないよう分割します。配列の並び順がそのまま実行順になるので、外部キーの参照先を先に置きます。

public function run(): void
{
    $this->call([
        ProductCategorySeeder::class,
        ProductSeeder::class,
        OrderSeeder::class,
    ]);
}

基底クラスにはcallのほかにcallWith・callSilent・callOnceが用意されています。callSilentは実行ログを出さない版、callWithは子シーダーのrunへ引数を渡す版です。実務で効いてくるのはcallOnceで、これは実行済みクラスの記録を見て二度目を飛ばします。複数のシーダーが同じマスタ用シーダーへ依存しているとき、それぞれが素直にcallすると重複投入が起きますが、callOnceへ替えるだけで解消します。

public function run(): void
{
    $this->callOnce(ProductCategorySeeder::class);

    Product::factory()->count(50)->create();
}

13系の既定シーダーはWithoutModelEventsを最初から使う

配布版のDatabaseSeederを開くと、Illuminate\Database\Console\Seeds\WithoutModelEventsがすでにuseされています。このトレイトが付いている間、投入で作られるモデルはcreatedやsavedといったイベントを発火しません。callで呼ばれた子シーダーにも効果が及びます。

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    public function run(): void
    {
        User::factory()->create([
            'name' => 'Test User',
            'email' => '[email protected]',
        ]);
    }
}

ここは既存記事の説明と実物がずれやすいところです。Laravel 8期の解説では「イベントを止めたいならトレイトを足す」と書かれていますが、13系では逆で、既定で止まっている状態から始まります。オブザーバでスラッグを自動採番している、あるいは作成時に通知を飛ばしている実装だと、シーディングした結果に採番も通知も乗りません。オブザーバ側の処理まで含めて検証データを作りたいなら、そのシーダーではトレイトを外すか、runの中で明示的に処理を呼び直す必要があります。モデルイベントとオブザーバの前提はLaravel Eloquentとは|モデル定義・リレーション・eager loadingの基礎で扱っているモデル定義側の話と地続きです。

Factoryと組み合わせてダミーデータをまとめて生成する手順

マスタのように件数が決まっているデータはSeederへ直接書けますが、一覧画面のページングやソートを確かめたいときは数十件から数百件が要ります。属性を1件ずつ書くのは現実的でないので、ここからFactoryへ寄せます。

definitionで既定値を決めてstateで差分だけを切り替える

Factoryはmake:factoryで作り、database/factoriesへ置かれる仕組みです。definitionが返す配列が既定の属性で、値の生成にはfakeヘルパ経由でFakerを呼び出せる形です。配布版のUserFactoryがそのまま参考になり、パスワードは静的プロパティへ代入する形で1回だけハッシュ化して使い回しています。ハッシュ化はそれなりに重い処理なので、100件生成するときの体感が変わります。

class ProductFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->words(3, true),
            'price' => fake()->numberBetween(100, 50000),
            'published_at' => now(),
        ];
    }

    public function draft(): static
    {
        return $this->state(fn (array $attributes) => [
            'published_at' => null,
        ]);
    }
}

差分はstateで切り出します。上のdraftのように名前を付けておけば、呼び出し側はdraftを連結するだけで下書き状態のレコードを作れます。ソフトデリート済みのレコードが欲しいときはtrashedが最初から使えるので、自前で定義する必要はありません。モデルとFactoryの対応はクラス名の規約で解決されますが、名前空間を切って外れる場合はモデル側にUseFactory属性を付けるか、Factory側にUseModel属性を付けて明示します。

hasとforとrecycleでリレーション込みの構造を組み立てる

関連レコードは呼び出し側の連結で作れます。親から子へはhas、子から親へはforです。マジックメソッドを使えば関連名をメソッド名に埋め込めます。

// 顧客1件につき注文3件
User::factory()->count(10)->hasOrders(3)->create();

// 既存の顧客へ注文をぶら下げる
$user = User::factory()->create();
Order::factory()->count(5)->for($user)->create();

厄介なのは、同じ親を共有すべき関連が2経路ある場合です。注文が顧客と配送先に紐づき、配送先も顧客に紐づく、といった構造をそのまま生成すると、注文の顧客と配送先の顧客は別人です。これを揃えるのがrecycleで、渡したインスタンスを関連の生成すべてで使い回します。コレクションを渡した場合は、その中からランダムに1件が選ばれます。

Order::factory()
    ->recycle(User::factory()->create())
    ->count(20)
    ->create();

sequenceで属性を振り分けて偏りのない検証データにする

Fakerに任せると値が完全にランダムになり、ステータスの分布が偏った検証データになりがちです。件数の配分を決めたいときはSequenceを使います。配列を並べた分だけ順番に適用され、10件生成すれば交互に5件ずつになります。

use Illuminate\Database\Eloquent\Factories\Sequence;

Order::factory()
    ->count(10)
    ->state(new Sequence(
        ['status' => 'paid'],
        ['status' => 'pending'],
    ))
    ->create();

クロージャを渡せば、通し番号を参照した値も作成可能です。クロージャが受け取るインスタンスのindexプロパティに、そこまでの反復回数が入っています。連番の伝票番号や日付をずらした履歴データを作るときはこの形が手早い方法です。生成後に追加処理を挟みたい場合は、Factory側にconfigureを定義してafterMakingとafterCreatingを登録します。ファイルの添付やピボットの調整はここへ寄せると呼び出し側が短く保てます。

実行局面ごとにdb:seedとmigrate:freshを使い分ける

コマンドは3系統あり、どこまで壊すかで選びます。手元の環境ならどれを使っても復旧できますが、共有の検証環境では差が出ます。

classオプションで1本だけ流す場面と全部流す場面を分ける

db:seedのオプションは--class・--database・--forceの3つだけです。--classの既定値がDatabase\Seeders\DatabaseSeederで、引数として直接クラス名を渡す書き方も受け付けます。

php artisan db:seed

php artisan db:seed --class=ProductCategorySeeder

php artisan db:seed --database=analytics

マスタを1本足したときに全体を流し直すと、ダミーデータまで増殖します。追加したクラスだけを--classで指定するのが普段の運用です。接続を複数持つ構成なら--databaseで流し先を切り替えられます。

freshは作り直しでrefreshは戻してから再実行する動き

スキーマごと作り直すならmigrate:freshに--seedを付ける手順です。全テーブルを落としてからマイグレーションを流し直し、続けてシーダーが走ります。実行するシーダーを1本に絞りたいときは--seederを併用します。

php artisan migrate:fresh --seed

php artisan migrate:fresh --seed --seeder=ProductCategorySeeder

php artisan migrate:refresh --seed --step=2

migrate:refreshのほうはロールバックを経由して戻すため、downが正しく書かれていることが前提になります。--stepで戻す本数を指定でき、直近数本だけを試すときに向きます。テーブルを落とさない代わりに、マイグレーションの外で作ったビューやトリガーは残ったままです。逆にmigrate:freshは--drop-viewsと--drop-typesを持ち、ビューやPostgreSQLの型まで含めて掃除できます。カラム定義側でenumを扱っているときの戻し方はLaravel migrationでenumを扱う実践ガイド【13対応】に整理があります。

テストからの呼び出しはSeed属性とseedメソッドで足りる

フィーチャーテストでSeederを走らせたい場合、テスト内からseedメソッドを呼びます。引数なしならDatabaseSeeder、クラス名を渡せば個別、配列を渡せば複数本です。

public function test_orders_can_be_created(): void
{
    $this->seed(OrderStatusSeeder::class);

    // 以降でマスタが入った前提のテストを書く
}

毎回同じマスタが要るなら、基底テストクラスへIlluminate\Foundation\Testing\Attributes\Seed属性を付けます。RefreshDatabaseを使うテストの前にDatabaseSeederが自動で走るようになり、個別のテストクラスで別のシーダーを指定したいときはSeeder属性を被せます。以前の版でテストクラスのプロパティとして書いていた指定は、属性へ寄った形です。

#[Seed]
abstract class TestCase extends BaseTestCase
{
}

#[Seeder(OrderStatusSeeder::class)]
class OrderTest extends TestCase
{
    use RefreshDatabase;
}

ここで気を付けたいのは実行コストです。RefreshDatabaseはスキーマが最新ならトランザクションで包むだけの軽い動作ですが、Seed属性を足すとテスト1本ごとにシーダーが走ります。マスタが数十件なら誤差でも、Factoryで数百件生成するシーダーがぶら下がっていると全体の実行時間が跳ねる原因です。全テスト共通で要るのはマスタだけに絞り、ダミーデータは各テストが自分のFactory呼び出しで作る形へ分けたほうが、結果的に速く済みます。

本番データベースへ流してよいSeederの線引きを先に決める

ここからが受託案件で実際に判断を求められる部分です。Seederは実行履歴を持たないため、マイグレーションのように未実行のものだけ走るという守りが効きません。本番で使うなら、その前提で書き分けておく必要があります。

本番事故を避けるためマスタデータとダミーデータは最初からクラスを割っておく

本番へ持っていってよいのは、アプリケーションの動作に必要な定義データだけです。都道府県、税区分、権限ロール、ステータスの一覧といった、コード側が値を前提にしているものが該当します。逆にテストユーザー、サンプル注文、ダミー商品は本番に存在してはいけないデータです。

この2種類が同じDatabaseSeederにぶら下がっていると、本番でdb:seedを打った瞬間に両方入ります。最初から呼び出しの入口を分けておくのが確実です。

// MasterSeeder ... 本番でも流す
public function run(): void
{
    $this->call([
        PrefectureSeeder::class,
        TaxRateSeeder::class,
        RoleSeeder::class,
    ]);
}

// DatabaseSeeder ... ローカルと検証のみ
public function run(): void
{
    $this->call([
        MasterSeeder::class,
        DemoUserSeeder::class,
        DemoOrderSeeder::class,
    ]);
}

本番ではMasterSeederだけを--classで指定する運用です。デプロイスクリプトに書くコマンドもこの1行に固定しておけば、手打ちでDatabaseSeederを走らせる余地が減ります。クラス名で意図が読めるので、引き継ぎ時の説明も短く済みます。

本番で走らせるSeederは何度流しても同じ結果になる形にする

マスタに項目を1件足すたび、そのシーダーは再実行の対象です。insertで書いていると2回目に重複が積み上がるか、ユニーク制約に当たって落ちます。本番用のSeederは、既にある行を更新し、無い行だけ挿入する形で書きます。

foreach ($rows as $row) {
    TaxRate::updateOrCreate(
        ['code' => $row['code']],
        ['rate' => $row['rate'], 'name' => $row['name']],
    );
}

件数が増えて1件ずつのクエリが重くなってきたら、まとめて書き込む形へ切り替える段階です。一意キーを指定して挿入と更新を1文で処理する方法はLaravel upsertの使い方|uniqueBy指定とupdateOrCreateとの使い分けにまとめてあるので、そちらの判断基準をそのまま持ち込めます。数万件規模の投入で実行時間が読めないなら、シーダーから直接回さずジョブへ逃がす選択肢もあり、その組み方はLaravel Queueの使い方|ジョブ定義からworker常駐と失敗ジョブ運用までで扱っています。

もう一点、削除の扱いを決めておきます。マスタから項目が消えたとき、Seederを流しても古い行は残る挙動です。運用側で消す前提にするのか、シーダーが差分を消しにいくのかは、参照している業務データの有無で変わります。既存の注文が古い税区分を参照している状態で行を消せば整合が壊れるため、多くの案件では論理削除フラグを立てる形へ寄せるのが無難です。

prohibitDestructiveCommandsはdb:seedを止めてくれない

本番でdb:seedを打つと、実行前に確認プロンプトが出ます。非対話のデプロイスクリプトから流す場合は--forceが要ります。ここまでは公式ドキュメントに書かれているとおりです。

php artisan db:seed --class=MasterSeeder --force

誤解しやすいのがDB::prohibitDestructiveCommandsとの関係です。これはサービスプロバイダで有効にしておくと破壊的なコマンドを禁止する仕組みで、実測すると対象はmigrate:fresh・migrate:refresh・migrate:reset・migrate:rollback・db:wipeの5本でした。db:seedはこの列挙に入っていません。つまり本番で--force付きのdb:seedを止める仕組みは、フレームワーク側には用意されていないという理解が要ります。

public function boot(): void
{
    DB::prohibitDestructiveCommands($this->app->isProduction());
}

守りを足すなら、シーダー側で環境を見て自分で止めるのが手早い方法です。ダミーデータ用のシーダーの先頭に本番判定を1行入れておけば、クラス名を打ち間違えても被害が出ません。

public function run(): void
{
    if (app()->isProduction()) {
        $this->command->warn('本番環境のため中断しました');

        return;
    }

    User::factory()->count(50)->create();
}

Seederを使わずマイグレーションへ寄せる判断と見送りの条件

そもそもSeederを本番で使わない選択もあります。マスタの初期値をマイグレーションファイルの中に書いてしまう方法で、こちらは実行履歴がmigrationsテーブルに残るため、二度流れる心配がありません。値の投入がスキーマ変更と同時に必要な場合、たとえば追加したカラムに既存行の初期値を埋める処理は、マイグレーションへ書くほうが順序の取り違えが起きにくくなります。

採用の線引きはこう置く方針です。値が変わらない定義データで、追加や修正のたびにコードレビューを通したいならマイグレーション側へ寄せます。運用担当が管理画面から編集する前提のデータや、環境ごとに中身が変わるデータはSeeder側に置き、本番では初回だけ流す運用です。ダミーデータは本番へ持ち込まないので、Seederだけで完結します。この3分類が決まっていれば、後から迷う場面はほとんど出ません。

見送ってよい場面もはっきりしています。マスタが管理画面から入力される運用で、初期値も営業側がCSVで用意するなら、Seederを書くより取り込み機能を先に作ったほうが早く終わります。テーブルが数本しかなく、検証データも数件で足りる規模なら、Factoryを整備するコストが回収できません。判断が付かないまま構成だけ増やすと保守側の負担になるため、規模と運用体制から先に決めてください。データ構造の設計から初期データの運用まで含めて相談したい場合は、業務用・Webアプリ開発で受託開発として引き受けています。

よくある質問

db:seedを実行してもデータが入らないのはなぜですか?

DatabaseSeederのrunから子シーダーを呼んでいない場合が最も多いパターンです。make:seederでクラスを作っただけでは、db:seedの対象になりません。callの配列へ追加するか、--classで直接指定してください。クラスを追加した直後に見つからないと言われる場合は、オートロードのキャッシュが古い可能性があるのでcomposer dump-autoloadを一度実行します。

2回目のシーディングでユニーク制約に引っかかる原因と実践的な直し方

Seederには実行履歴がないため、insertで書いた処理は流した回数だけ挿入されます。開発中はmigrate:freshに--seedを付けて作り直すのが手早く、本番用のシーダーはupdateOrCreateやupsertで書き換えます。既存行を消してから入れ直す実装は、外部キーで参照されていると失敗するため避けたほうが安全です。

Seederの中でモデルのイベントを発火させたい場合の実行方法

13系の配布版DatabaseSeederはWithoutModelEventsを最初から使っており、これが子シーダーにも効きます。オブザーバの処理まで含めて検証したいなら、そのトレイトのuseを外してください。全体では止めたまま一部だけ発火させたい場合は、対象のシーダーをDatabaseSeeder経由ではなく--class指定の単独実行へ分けます。

本番環境でSeederを実行しても問題ありませんか?

マスタデータ専用のシーダーで、何度実行しても同じ結果になる書き方であれば実行できます。非対話で流すには--forceが必要です。ダミーデータを含むシーダーは対象外にし、クラスを分けたうえで本番判定による中断を仕込んでおくと事故が防げます。

FactoryとSeederはどちらから書き始めるべきですか?

件数が固定のマスタはSeederへ直接書き、件数が可変のデータはFactoryを先に用意します。マスタが数十件でモデル経由の生成が要らないなら、Factoryを作らずクエリビルダで済ませて構いません。一覧やページングの検証で数百件が要る段階になってからFactoryを足す順序で足ります。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.16 テックブログ バーガーキング不正アクセスで約1,118万件の情報漏洩|対象確認・案内メールの真偽と運営側のログ点検
  2. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更の手順
  3. 2026.10.16 テックブログ ソースネクスト不正アクセスで最大2,252万件流出|ログインできない理由・パスワード再設定と会員APIの点検
  4. 2025.12.02 テックブログ クーパン問題とは?3,755万人流出と過去最大6,246億ウォンの課徴金
  5. 2026.10.15 テックブログ リゾートトラスト不正アクセス:会員2.6万件・カハラ横浜3.6万件の対象確認と窓口

RELATED POSTS 関連記事

目次