はじめに
先日、Laravel でポリモフィック関連の Factory を作成してみた という記事を書きました。
その際に、DatabaseSeeder.php に全てのテーブルの Seed データを記載しました。
ただ、これだと DatabaseSeeder.php が肥大化してコード確認や修正、追加が大変だなと感じました。
なので、今回は、この DatabaseSeeder.php を各 Seeder ファイルに分解する方法を記事にしようと思います。
DatabaseSeeder を作成時に注意点もあるので、最後にこれについても記述します。
この記事を読んだら、できるようになること
- 肥大化した DatabaseSeeder.php を分解できる。
- 分解したクラス(Seeder クラス)を呼び出すことができる。
肥大化した DabaseSeeder.php の確認
DatabaseSeeder のコード
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Comment;
use App\Models\Image;
use App\Models\Post;
use App\Models\User;
use App\Models\Video;
use Illuminate\Database\Seeder;
final class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run(): void
{
// Post
Post::factory()
->count(3)
->has(Image::factory())
->create();
Post::factory()
->count(3)
->has(Comment::factory())
->create();
// User
User::factory()
->count(3)
->has(Image::factory())
->create();
// Image
Image::factory()
->count(3)
->for(
Post::factory(),
'imageable'
)->create();
Image::factory()
->count(3)
->for(
User::factory(),
'imageable'
)->create();
// Video
Video::factory()
->count(3)
->has(Comment::factory())
->create();
// Comment
Comment::factory()
->count(3)
->for(
Post::factory(),
'commentable'
)->create();
Comment::factory()
->count(3)
->for(
Video::factory()
->count(2),
'commentable'
)->create();
}
}
ご覧のとおり、冗長ですよね。
まだデータが少ないので、修正やデータの確認は「ちょっとめんどくさいな」程度だと思いますが、
これが何千行もあるとゾッとしますよね。
余談ですが、私は、30,000 行のコードを見たことがありますw
必要な情報を探すだけでも苦労しました…
肥大化する前にデータの整理が大切だなと感じました。
やりたいこと
肥大化した DatabaseSeeder.php を各 Seeder.php に分解する。
0. 実装の流れ
- 各Seeder ファイルを作成
- DatabaseSeeder.php のデータを 1. で作った Seeder ファイルに移動する
- DatabaseSeeder.php から各 Seeder を呼び出す
- DB を確認
最終的なDatabaseSeeder.php はこんな感じです。
<?php
declare(strict_types=1);
namespace Database\Seeders;
use Illuminate\Database\Seeder;
final class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run(): void
{
$this->call([
PostSeeder::class,
UserSeeder::class,
VideoSeeder::class,
ImageSeeder::class,
CommentSeeder::class,
]);
}
}
すごくスッキリしたかと思います。
Seed データ修正したいときは、該当の Seeder ファイルを修正するだけです。
1. 各Seeder を作成
DatabaseSeeder.php を分解していきたいと思いますが、
その前に、必要な Seeder.php ファイルを下記のコマンドで作成します。
php artisan make:seeder 〇〇〇Seeder
今回は作成するのは、ImageSeeder, PostSeeder, CommentSeeder, UserSeeder, VideoSeeder です。
2. DatabaseSeeder.php のデータを 1. で作った Seeder ファイルに移動する
各 Seeder ファイルに DatabaseSeeder のデータをコピーします
ImageSeeder
<?php declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Image;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Seeder;
final class ImageSeeder extends Seeder
{
public function run(): void
{
Image::factory()
->count(3)
->for(
Post::factory(),
'imageable'
)->create();
Image::factory()
->count(3)
->for(
User::factory(),
'imageable'
)->create();
}
}
PostSeeder
PostSeede.php のコード
※やることは ImageSeeder と同じなので折りたたんでいます。興味あれば見てみて下さいm(_ _)m。
<?php declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Comment;
use App\Models\Image;
use App\Models\Post;
use Illuminate\Database\Seeder;
final class PostSeeder extends Seeder
{
public function run(): void
{
Post::factory()
->count(3)
->has(Image::factory())
->create();
Post::factory()
->count(3)
->has(Comment::factory())
->create();
}
}
CommentSeeder
CommentSeeder.php のコード
<?php declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Comment;
use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Seeder;
final class CommentSeeder extends Seeder
{
public function run(): void
{
Comment::factory()
->count(3)
->for(
Post::factory(),
'commentable'
)->create();
}
}
UserSeeder
UserSeeder.php のコード
<?php declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Image;
use App\Models\User;
use Illuminate\Database\Seeder;
final class UserSeeder extends Seeder
{
public function run(): void
{
User::factory()
->count(3)
->has(Image::factory())
->create();
}
}
VideoSeeder
CommentSeeder.php のコード
<?php declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Comment;
use App\Models\Video;
use Illuminate\Database\Seeder;
final class VideoSeeder extends Seeder
{
public function run(): void
{
Video::factory()
->count(3)
->has(Comment::factory())
->create();
}
}
DatabaseSeeder
DatabaseSeeder.php のデータを削除します。
DarabseSeeder.php のコード
<?php
declare(strict_types=1);
namespace Database\Seeders;
- use App\Models\Comment;
- use App\Models\Image;
- use App\Models\Post;
- use App\Models\User;
- use App\Models\Video;
use Illuminate\Database\Seeder;
final class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run(): void
{
- // Post
- Post::factory()
- ->count(3)
- ->has(Image::factory())
- ->create();
-
- Post::factory()
- ->count(3)
- ->has(Comment::factory())
- ->create();
-
- // User
- User::factory()
- ->count(3)
- ->has(Image::factory())
- ->create();
-
- // Image
- Image::factory()
- ->count(3)
- ->for(
- Post::factory(),
- 'imageable'
- )->create();
-
- Image::factory()
- ->count(3)->for(
- User::factory(),
- 'imageable'
- )->create();
-
- // Video
- Video::factory()
- ->count(3)
- ->has(Comment::factory())
- ->create();
-
- // Comment
- Comment::factory()
- ->count(3)
- ->for(
- Post::factory(),
- 'commentable'
- )->create();
-
- Comment::factory()
- ->count(3)
- ->for(
- Video::factory()
- ->count(2),
- 'commentable'
- )->create();
}
}
3. DatabaseSeeder.php から各 Seeder を呼び出す
先程削除した DatabaseSeeder.php に各 Seeder を呼び出すコードを記述します。
記述方法は簡単で、
-
run
メソッドにCall
メソッドを追加 -
call
メソッドに配列内で呼び出したい Seed クラスを記述
これだけです。
<?php
declare(strict_types=1);
namespace Database\Seeders;
use Illuminate\Database\Seeder;
final class DatabaseSeeder extends Seeder
{
public function run(): void
{
+ $this->call([
+ ImageSeeder::class,
+ PostSeeder::class,
+ CommentSeeder::class,
+ UserSeeder::class,
+ VideoSeeder::class,
+ ]);
}
}
4. DB を確認
最後に DB にデータが追加されているかを確認します。
DatabaseSeeder.php の Call メソッド使用時の注意点
call
メソッドの配列の Seed クラスの記述順に注意が必要です。
まだ作成していないオブジェクトを呼び出そうとするとエラーとなります。
当たり前のことですが、僕はこれに引っかかってしまいました。
具体例
Image のデータを親の Post の id=1
を用いて作りたいとします。
DatabaseSeeder.php には、
ImageSeeder の呼び出しの後に PostSeeder を呼び出しています。
<?php
declare(strict_types=1);
namespace Database\Seeders;
use Illuminate\Database\Seeder;
final class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
ImageSeeder::class, // ← ここ
PostSeeder::class, // ← ここ
CommentSeeder::class,
UserSeeder::class,
VideoSeeder::class,
]);
}
}
Image データを Post の id=1
を指定して作成します。
<?php declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Image;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Seeder;
final class ImageSeeder extends Seeder
{
public function run(): void
{
// Post の id=1 を指定して作成
Image::factory()
->count(3)
->for(
Post::find(1),
'imageable'
)->create();
}
}
実行してみると
php artisan migrate:fresh --seed
Error
Call to a member function getMorphClass() on null
とエラーが出てしまいます。
これは、親(Post)がいないのに 子(Image)を作成しようとしているのでエラーが出ちゃいました。
なので、子(Image)を作る前に親(Post)を作成しちゃえばエラーを解決できます。
<?php
declare(strict_types=1);
namespace Database\Seeders;
use Illuminate\Database\Seeder;
final class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
+ PostSeeder::class, // ← 子クラスよりも前にもってくる
ImageSeeder::class,
- PostSeeder::class,
CommentSeeder::class,
UserSeeder::class,
VideoSeeder::class,
]);
}
}
サンプルは、記述量がすこしですが、
実務の膨大な量の Seeder クラスがあるときは、記述順に注意が必要ですね。
親がいないと子は作れない
と覚えておくといいですね。
さいごに
コードが肥大化して、メンテナンス性が落ちてしまうので、積極的にコードの分解を意識していきたいと思います。