Laravelによる書籍管理システムの作成、第3回です。
今回は「Seed機能とFakerを用いてのテストデータの登録」についてポストしたいと思います。
1. データ作成前の確認
1-1. モデルファイルへのトレイト追加(確認)
Seed機能を使用する際は、対象となるモデルファイルにトレイトが読み込まれていることを確認してください。
これまでの手順でモデルファイルを make:model コマンドで作成していれば、デフォルトで入っているはずですが、念の為確認します。
記述がない場合は、追記してください。
1-1-1. Categoryモデル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; // ←★これ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Category extends Model { use HasFactory; // ←★これ /** * モデルに関連付けるテーブル * * @var string */ protected $table = 'm_categories'; // ここを 'm_categories' に設定 protected $fillable = [ 'category_name', 'is_deleted' ]; /** * 書籍とのリレーション (1対多) * 1つのカテゴリは複数の書籍を持つ */ public function books(): HasMany { // 第2引数は相手(books)テーブルの外部キー(category_id) return $this->hasMany(Book::class, 'category_id'); } } |
1-1-2. Publisherモデル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; // ←★これ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Publisher extends Model { use HasFactory; // ←★これ /** * モデルに関連付けるテーブル * * @var string */ protected $table = 'm_publishers'; // ここを 'm_publishers' に設定 protected $fillable = [ 'publisher_name', 'publisher_kana', 'memo', 'is_deleted' ]; /** * 書籍とのリレーション (1対多) * 1つの出版社は複数の書籍を持つ */ public function books(): HasMany { // 第2引数は相手(books)テーブルの外部キー(publisher_id) return $this->hasMany(Book::class, 'publisher_id'); } } |
両ファイルともコマンドからモデルファイルを作成しているので、デフォルトで記述されていました。
2. ファクトリファイルの作成
一覧画面など作成する前に、開発しやすくするためにテストデータをデータベースに登録しようと思います。
まずはマスタテーブルである「カテゴリ」と「出版社」テーブルのデータを作成します。
コマンドから作成したファクトリファイルは、/database/factories/ に作成されます。
2-1. CategoryFactory
Ubuntu のターミナルから下記コマンドを実行して「ファクトリファイル」を作成します。
|
1 2 3 4 |
sail artisan make:factory CategoryFactory --model=Category # 成功したら以下のメッセージが出る INFO Factory [database/factories/CategoryFactory.php] created successfully. |
2-2. PublisherFactory
同様に出版社のファクトリファイルも作成します。
|
1 2 3 4 |
sail artisan make:factory PublisherFactory --model=Publisher # 成功したら以下のメッセージが出る INFO Factory [database/factories/PublisherFactory.php] created successfully. |
3. ファクトリファイルの編集
Seeder で Factory を使用すると、複数データを用意する場合に for文 を書かなくていいので簡潔にコードが書け、リレーションがあっても簡潔に書けるとのことです。 ここはぜひとも Laravel の流儀を理解する意味も込めて Factory と Seeder を使ってみたいと思います。
Factory と Seeder についての公式ドキュメントは以下を参照。
3-1. config/app.php の設定編集
それっぽい疑似フェイクデータを自動生成してくれる便利なライブラリ「Faker」を使用してデータを作成します。
Laravel は同梱してインストールしてくれているようなので、特に意識せずに使用できたりします。
まずは下準備として、config\app.php の faker_locale を「en_US」から「ja_JP」に変更します。
‘faker_locale’ => env(‘APP_FAKER_LOCALE’, ‘en_US’),
↓↓↓↓↓ 変更 ↓↓↓↓↓
‘faker_locale’ => env(‘APP_FAKER_LOCALE’, ‘ja_JP’),
これで生まれるテストデータが日本語になってくれます。
これ、地味にものすごく便利でした。
Fakerの使い方について、チートシートは以下を参照。
3-2. CategoryFactory.php の編集
編集ファイル:database\factories\CategoryFactory.php
カテゴリ名は重複禁止(unique)にしているため、Fakerでユニークな単語を生成するようにします。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Category> */ class CategoryFactory extends Factory { /** * Define the model's default state. * * @return array<string, mixed> */ public function definition(): array { return [ // unique()をつけて重複エラーを防ぐ 'category_name' => fake()->unique()->word(), 'is_deleted' => false, 'created_at' => now(), 'updated_at' => now(), ]; } } |
3-3. PublisherFactory.php の編集
編集ファイル:database\factories\PublisherFactory.php
出版社名もユニーク制約があるため unique() を使用します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Publisher> */ class PublisherFactory extends Factory { /** * Define the model's default state. * * @return array<string, mixed> */ public function definition(): array { return [ // 会社名を生成 'publisher_name' => fake()->unique()->company(), // カナはFaker標準だと会社名カナがないため、個人のカナ名などで代用 // (または単純なカタカナ文字列生成) 'publisher_kana' => fake()->lastKanaName(), // 備考にランダムな文章 'memo' => fake()->realText(50), 'is_deleted' => false, 'created_at' => now(), 'updated_at' => now(), ]; } } |
4. Seederの作成と実行
4-1. 各テーブルのSeeder作成
Factoryを作っただけではデータは入らないので、Seederファイルを作成して実行指示を書きます。
Ubuntu のターミナルから下記コマンドを実行して、Seederファイルを作成します。
|
1 2 3 4 5 |
# カテゴリ sail artisan make:seeder CategorySeeder # 成功したら以下のメッセージが出る INFO Seeder [database/seeders/CategorySeeder.php] created successfully. |
|
1 2 3 4 5 |
# 出版社 sail artisan make:seeder PublisherSeeder # 成功したら以下のメッセージが出る INFO Seeder [database/seeders/PublisherSeeder.php] created successfully. |
4-2. Seederファイルの編集
4-2-1. CategorySeeder
ここではランダムな文字列ではなく、実際のアプリで使いそうなカテゴリ名を指定して登録する例を書きます(Sequence機能)。
編集ファイル: database/seeders/CategorySeeder.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\Category; class CategorySeeder extends Seeder { public function run(): void { // 具体的なカテゴリ名を指定して作成する場合 Category::factory() ->count(6) ->sequence( ['category_name' => '文芸書'], ['category_name' => '実用書'], ['category_name' => 'ビジネス書'], ['category_name' => '専門書'], ['category_name' => '絵本・児童書'], ['category_name' => 'コミック'], ) ->create(); // ※ ランダムで良い場合は以下のように書くだけです // Category::factory()->count(10)->create(); } } |
4-2-2. PublisherSeeder
出版社はランダムに10件作成します。
編集ファイル:database/seeders/PublisherSeeder.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\Publisher; class PublisherSeeder extends Seeder { public function run(): void { Publisher::factory()->count(10)->create(); } } |
4-3. DatabaseSeederへの登録
最後に、大元の DatabaseSeeder.php からこれらを呼び出すようにします。
編集ファイル:database/seeders/DatabaseSeeder.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { public function run(): void { // 作成したSeederを呼び出す $this->call([ CategorySeeder::class, PublisherSeeder::class, ]); } } |
5. データ作成コマンドの実行
以下のコマンドでSeederを実行し、データを作成します。
sail artisan db:seed
成功すると、下記のメッセージが表示されます。
|
1 2 3 4 5 6 7 |
INFO Seeding database. Database\Seeders\CategorySeeder ................................................................................ RUNNING Database\Seeders\CategorySeeder ............................................................................ 225 ms DONE Database\Seeders\PublisherSeeder ............................................................................... RUNNING Database\Seeders\PublisherSeeder ........................................................................... 230 ms DONE |
もしDBを一度リセットして最初から作り直したい場合は sail artisan migrate:fresh --seed を使ってください。(後述します → 7-5-2)
6. データ作成結果
一度ここで phpMyAdmin からデータを確認します。
わたしの環境の場合は、下記URLです。
http://localhost:8081/
6-1. カテゴリテーブル
データ件数は6件。狙い通り、指定したデータが挿入されています。
6-2. 出版社テーブル
データ件数10件、こちらもいい感じにダミーデータが挿入されています。
出版社名へは適当な会社名が入っており、出版社名かなとは連動は取れていませんが、いい感じのダミーとなっていますね。
また、備考カラムへは、「銀河鉄道の夜」の文章が入っています。
7. 書籍、著者、タグテーブルのダミーデータを作成
一通りダミーデータの作成方法がわかったところで、書影テーブル以外の残りのテーブルのダミーデータも作成します。
これらは互いにリレーション(親子関係)があるため、「BookFactoryの中で親(Category/Publisher)をどう扱うか」と「子(Author/Tag)側で親(Book)をどう定義するか」がポイントになります。
また、書影テーブルは画像のパス(もしくはファイル名)を保存するテーブル少し特殊なテーブルのため、ここではデータは作成しないことにします。
7-1. BookFactory.php
書籍は Category と Publisher に依存しています。
Factoryの定義内で Category::factory() のように記述することで、書籍作成時に 自動的に親データも生成する 設定にします。
7-1-1. ファクトリファイルの作成コマンド
sail artisan make:factory BookFactory
7-1-2. database/factories/BookFactory.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Category; use App\Models\Publisher; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Book> */ class BookFactory extends Factory { public function definition(): array { return [ // 外部キー:定義がない場合、自動的にCategoryとPublisherのFactoryを呼んで生成する 'category_id' => Category::factory(), 'publisher_id' => Publisher::factory(), 'title' => fake()->realText(20), // 20文字程度のリアルなテキスト 'title_kana' => fake()->lastKanaName(), // ※完全な書名カナの生成は難しいため、便宜上カナ名を使用 'subtitle' => fake()->boolean(30) ? fake()->realText(15) : null, // 30%の確率でサブタイトルあり 'volume_number' => fake()->randomElement([null, '1', '2', '上', '下']), 'isbn_code' => fake()->isbn13(), 'publication_year' => fake()->year(), 'price' => fake()->numberBetween(500, 5000), // 500円〜5000円 'description' => fake()->realText(100), 'memo' => fake()->realText(30), 'is_deleted' => false, 'created_at' => now(), 'updated_at' => now(), ]; } } |
7-2. AuthorFactory.php
著者は必ず Book に紐づきます。
定義内で Book::factory() とすることで、著者単体で Factory を実行した際にも、親となる書籍が自動生成されます。
7-2-1. ファクトリファイルの作成コマンド
sail artisan make:factory AuthorFactory
7-2-2. database/factories/AuthorFactory.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Book; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Author> */ class AuthorFactory extends Factory { public function definition(): array { return [ // 親となる書籍を自動生成(Seederで上書き可能) 'book_id' => Book::factory(), 'author' => fake()->name(), 'author_kana' => fake()->kanaName(), 'author_job' => fake()->randomElement(['著', '訳', '絵', '監修', '原作']), 'is_deleted' => false, 'created_at' => now(), 'updated_at' => now(), ]; } } |
7-3. TagFactory.php
タグも同様に Book に紐づきます。
7-3-1. 作成コマンド
sail artisan make:factory TagFactory
7-3-2. database/factories/TagFactory.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Book; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tag> */ class TagFactory extends Factory { public function definition(): array { return [ 'book_id' => Book::factory(), 'tag' => fake()->randomElement([ 'ミステリー', 'SF', '恋愛', '歴史', '技術書', 'プログラミング', 'ファンタジー', 'ホラー', '自己啓発' ]), 'is_deleted' => false, 'created_at' => now(), 'updated_at' => now(), ]; } } |
7-4. 実行用 Seeder
これらを組み合わせて、「書籍を作りつつ、著者やタグも同時に作成する」 Seeder の記述例です。
ポイント: recycle() を使うことで、毎回新しいマスタ(出版社・カテゴリ)を作るのではなく、既存のマスタデータからランダムに選んで設定させることができます。
7-4-1. BookSeeder 作成コマンド
sail artisan make:seeder BookSeeder
database/seeders/BookSeeder.php 編集
この BookSeeder を実行することで、マスタとの整合性を保ちつつ、著者やタグが紐付いた書籍データが一気に作成されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use App\Models\Book; use App\Models\Category; use App\Models\Publisher; use App\Models\Author; use App\Models\Tag; class BookSeeder extends Seeder { public function run(): void { // 既存のカテゴリと出版社をすべて取得(Factoryでの使い回し用) $categories = Category::all(); $publishers = Publisher::all(); // 書籍を20冊作成 Book::factory() ->count(20) // recycleを使うと、新しく作らずに渡したコレクションからランダムに選んで割り当てる ->recycle($categories) ->recycle($publishers) // hasを使って、子リレーション(著者・タグ)も同時に作成 ->has(Author::factory()->count(1), 'authors') // 著者を1人つける(メソッド名は必ずBookモデルにあるメソッド名とすること) ->has(Tag::factory()->count(2), 'tags') // タグを2つつける(メソッド名は必ずBookモデルにあるメソッド名とすること) ->create(); } } |
7-4-3. has()を使うときのリレーションメソッド名に注意!
BookSeeder中にある、has() メソッドを使って親モデルと子モデルを同時に生成する場合、has(子モデル::factory(), 'リレーションメソッド名') の形で記述します。
この第2引数に指定するメソッド名 ('authors', 'tags' など) は、必ず 親モデル(Book.php)に定義されたリレーションメソッド名(public function authors(): HasMany)と完全に一致 している必要があります。
もしこのメソッドが存在しない場合 Call to undefined method App\Models\Book::authors() のようなエラーが発生し、テストデータの生成は失敗します。
このエラーが出た場合は、該当するリレーションメソッドがBookモデルに定義されているか を必ず確認してください。
7-5. BookSeederの登録と実行
7-5-1. DatabaseSeeder.php へ登録
BookSeederを実行するため、カテゴリと出版社のときにも行った、大本の DatabaseSeeder へ登録します。
編集ファイル:database/seeders/DatabaseSeeder.php
|
1 2 3 4 5 6 7 8 9 10 11 |
public function run(): void { $this->call([ // 1. 先に親(マスタ)を作る CategorySeeder::class, PublisherSeeder::class, // 2. その後に子(書籍)を作る BookSeeder::class, ]); } |
7-5-2. DatabaseSeederの実行
今回の場合ですが、(データ作成手順を説明するために)先にカテゴリと出版社のテーブルへデータを登録しているので、この登録されているデータをクリアした後に再度 Seeder を実行してデータを登録するということを行います。
開発中にはよくあることなので、覚えておくと幸せになれるかもしれません。
sail artisan migrate:fresh –seed
7-6. レコード作成結果
7-6-1. authors テーブル
7-6-2. books テーブル
7-6-3. tags テーブル
関連するポスト









