【Laravel】Seederでテストデータを入れる3つの方法

laravelアイキャッチ Laravel

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

前回は多対多リレーション(belongsToMany)を扱いました。

実際にリレーションの動きを確認しようとすると、記事・タグ・中間テーブルにそれぞれデータを手入力するのは大変ですよね。

そこで今回は、テストデータをまとめて投入できる Seeder を扱います。

Seederとは?

なぜ手動でデータを入れてはいけないのか

Seederとは、データベースにテスト用のデータをまとめて投入する仕組みです。

phpMyAdminなどのGUIツールで1件ずつ手入力することもできますが、これにはいくつか問題があります。

  • チームメンバーごとにデータの内容がバラバラになる
  • 環境を作り直すたびに、また手作業で入れ直す必要がある
  • 「このデータ、どうやって入れたんだっけ?」が分からなくなる

Seederを使えば、投入するデータの内容がコードとして残るため、誰が・いつ実行しても同じ状態を再現できます。

私が実務に入りたてのころ、ローカル環境を作り直すたびにテストデータを手打ちしていて、ある日「あのユーザー、パスワード何だっけ」と分からなくなったことがあります。

Seederの存在を知ってからは、この手間から完全に解放されました。

Seederの置き場所

Seederファイルは database/seeders/ に置かれます。

Laravelプロジェクトを作成すると、最初から DatabaseSeeder.php という起点となるファイルが用意されています。

database/
└── seeders/
    └── DatabaseSeeder.php

ここに他のSeederを呼び出す処理を書いていくのが基本の流れです。

方法1:直接データを書く(シンプルな固定データ向き)

基本の書き方

まずは一番シンプルな方法として、Seederファイルの中に直接データを書くやり方です。

管理者アカウントやカテゴリマスタなど、「いつでも同じ内容で入れたい固定データ」に向いています。

php artisan make:seeder TagSeeder
<?php
// database/seeders/TagSeeder.php

namespace Database\Seeders;

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

class TagSeeder extends Seeder
{
    public function run(): void
    {
        Tag::insert([
            ['name' => 'Laravel', 'created_at' => now(), 'updated_at' => now()],
            ['name' => 'PHP', 'created_at' => now(), 'updated_at' => now()],
            ['name' => 'MySQL', 'created_at' => now(), 'updated_at' => now()],
        ]);
    }
}

insert() を使うと、複数件のデータを1回のクエリでまとめて登録できます。

DatabaseSeederから呼び出す

作ったSeederは、DatabaseSeeder.phprun() メソッドから呼び出すことで実行対象になります。

<?php
// database/seeders/DatabaseSeeder.php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            TagSeeder::class,
        ]);
    }
}
php artisan db:seed

$this->call() に配列でSeederクラスを渡すことで、実行順序をコントロールしながらまとめて実行できます。

方法2:Factoryと組み合わせる(大量データ向き)

なぜ直接書く方法だけでは足りないのか

方法1は固定データには向いていますが、「ユーザーを100人分作りたい」というケースでは現実的ではありません。

こういった大量データの生成には、次回詳しく扱う Factory と組み合わせるのが定番です。

<?php
// database/seeders/UserSeeder.php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class UserSeeder extends Seeder
{
    public function run(): void
    {
        User::factory()->count(50)->create();
    }
}

User::factory()->count(50)->create() の1行だけで、ダミーの氏名・メールアドレスが入った50人分のユーザーが作成されます。

Factory自体の仕組みは次の記事で扱うので、ここでは「Seeder単体では固定データ向き、Factoryと組み合わせると大量データ向き」という役割分担だけ押さえておいてください。

方法3:CSVやJSONなど外部ファイルから読み込む

既存データを移行したいときに使う

社内に既にあるExcelやCSVのマスタデータを、そのままテストデータとして使いたい場面もあります。

そんなときは、外部ファイルを読み込んでSeederに流し込む方法が便利です。

<?php
// database/seeders/PrefectureSeeder.php

namespace Database\Seeders;

use App\Models\Prefecture;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\File;

class PrefectureSeeder extends Seeder
{
    public function run(): void
    {
        $path = database_path('seeders/data/prefectures.csv');
        $rows = array_map('str_getcsv', File::lines($path)->toArray());

        // 1行目はヘッダーなので除外する
        $header = array_shift($rows);

        foreach ($rows as $row) {
            $data = array_combine($header, $row);
            Prefecture::create([
                'code' => $data['code'],
                'name' => $data['name'],
            ]);
        }
    }
}
code,name
01,北海道
13,東京都
27,大阪府

File::lines() でCSVファイルを1行ずつ読み込み、str_getcsv でカンマ区切りの配列に変換しています。

都道府県マスタや業種マスタなど、「アプリの仕様として決まっている固定リスト」を扱うときに向いている方法です。

つまずきやすいポイント:外部キー制約でエラーになる

実行順序を意識していないと失敗する

複数のSeederを組み合わせたとき、私が実際にハマったのが外部キー制約のエラーです。

❌ Before:親テーブルより先に子テーブルのSeederを実行する

<?php
// database/seeders/DatabaseSeeder.php

public function run(): void
{
    $this->call([
        ArticleTagSeeder::class, // 中間テーブル(先に実行してしまう)
        TagSeeder::class,        // tagsテーブル
        ArticleSeeder::class,    // articlesテーブル
    ]);
}
SQLSTATE[23000]: Integrity constraint violation:
1452 Cannot add or update a child row: a foreign key constraint fails

article_tag テーブルは articlestags の両方のIDを参照しているため、参照先のデータがまだ存在しない状態でINSERTしようとすると、この外部キー制約エラーが発生します。

✅ After:参照される側(親)から順番に実行する

<?php
// database/seeders/DatabaseSeeder.php

public function run(): void
{
    $this->call([
        TagSeeder::class,        // 先にtagsテーブル
        ArticleSeeder::class,    // 先にarticlesテーブル
        ArticleTagSeeder::class, // 最後に中間テーブル
    ]);
}

$this->call() に渡す配列の順序が、そのまま実行順序になります。

「参照される側(親)を先に、参照する側(子)を後に」という原則を意識するだけで、この手のエラーは避けられます。

応用:特定のSeederだけを実行する

開発中に毎回全部実行すると時間がかかる

開発が進んでSeederの数が増えてくると、php artisan db:seed で毎回すべてを実行するのは非効率になってきます。

そんなときは、--class オプションで実行対象を絞り込めます。

# TagSeederだけを実行する
php artisan db:seed --class=TagSeeder

# マイグレーションをやり直してからSeederも実行する
php artisan migrate:fresh --seed

migrate:fresh --seed は、テーブルを全部作り直してから DatabaseSeeder を実行してくれるコマンドです。

「DB構造を変えたので、一度まっさらな状態から作り直したい」というときによく使うので、ぜひ覚えておいてください。

まとめ

この記事のポイント

  • Seederはテストデータをコードとして再現可能にする仕組み
  • 固定データは「直接書く」方法、大量データは「Factoryとの組み合わせ」が向いている
  • 既存の外部データはCSV・JSON読み込みで流し込める
  • 複数Seederを組み合わせるときは、外部キー制約を考えて親→子の順に実行する
  • migrate:fresh --seed でDBを作り直してからテストデータを再投入できる

次に読むべき記事

大量データを効率よく生成する仕組みとして、次はFactoryを詳しく見ていきましょう。

→ 次の記事:Factoryでダミーデータを効率よく作る

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