【Laravel】肥大化した DatabaseSeeder を分解する

## はじめに

先日、[Laravel でポリモフィック関連の Factory を作成してみた](https://qiita.com/daishiman/items/a612d9cdeb5065a7acb1) という記事を書きました。

その際に、DatabaseSeeder.php に全てのテーブルの Seed データを記載しました。

ただ、これだと **DatabaseSeeder.php が肥大化してコード確認や修正、追加が大変**だなと感じました。

なので、今回は、この DatabaseSeeder.php を各 Seeder ファイルに分解する方法を記事にしようと思います。

DatabaseSeeder を作成時に注意点もあるので、最後にこれについても記述します。

## この記事を読んだら、できるようになること

:::note info
- 肥大化した DatabaseSeeder.php を分解できる。
- 分解したクラス（Seeder クラス）を呼び出すことができる。
:::



## 肥大化した DabaseSeeder.php の確認

<details><summary>DatabaseSeeder のコード</summary>

```php:src/database/seeders/DatabaseSeeder.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();
    }
}
```

</details>

ご覧のとおり、冗長ですよね。

まだデータが少ないので、修正やデータの確認は「ちょっとめんどくさいな」程度だと思いますが、  
これが何千行もあるとゾッとしますよね。


余談ですが、私は、30,000 行のコードを見たことがありますw  
必要な情報を探すだけでも苦労しました…

肥大化する前にデータの整理が大切だなと感じました。


## やりたいこと

肥大化した DatabaseSeeder.php を各 Seeder.php に分解する。

### 0. 実装の流れ

1. 各Seeder ファイルを作成
2. DatabaseSeeder.php のデータを 1. で作った Seeder ファイルに移動する
3. DatabaseSeeder.php から各 Seeder を呼び出す
4. DB を確認

最終的なDatabaseSeeder.php はこんな感じです。

```php:src/database/seeders/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 ファイルを下記のコマンドで作成します。



```zsh
php artisan make:seeder 〇〇〇Seeder
```

今回は作成するのは、ImageSeeder, PostSeeder, CommentSeeder, UserSeeder, VideoSeeder です。


### 2. DatabaseSeeder.php のデータを 1. で作った Seeder ファイルに移動する

各 Seeder ファイルに DatabaseSeeder のデータをコピーします

#### ImageSeeder

```php:src/database/seeders/ImageSeeder.php
<?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

<details>
  <summary>
    PostSeede.php のコード
    <br>
    ※やることは ImageSeeder と同じなので折りたたんでいます。興味あれば見てみて下さいm(_ _)m。
</summary>

```php:src/database/seeders/PostSeeder.php
<?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();
    }
}
```

</details>

#### CommentSeeder

<details><summary>CommentSeeder.php のコード</summary>

```php:src/database/seeders/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();
    }
}
```

</details>

#### UserSeeder

<details><summary>UserSeeder.php のコード</summary>

```php:src/database/seeders/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();
    }
}
```

</details>

#### VideoSeeder

<details><summary>CommentSeeder.php のコード</summary>

```php:src/database/seeders/VideoSeeder.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();
    }
}
```

</details>

#### DatabaseSeeder
DatabaseSeeder.php のデータを削除します。

<details><summary>DarabseSeeder.php のコード</summary>

```diff:src/database/seeders/ImageSeeder.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();
    }
}
```
</details>


### 3. DatabaseSeeder.php から各 Seeder を呼び出す

先程削除した DatabaseSeeder.php に各 Seeder を呼び出すコードを記述します。

記述方法は簡単で、

1. `run` メソッドに `Call` メソッドを追加
2. `call` メソッドに配列内で呼び出したい Seed クラスを記述

これだけです。


```diff:src/database/seeders/ImageSeeder.php
<?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 クラスの記述順に注意が必要です。

:::info alert
まだ作成していないオブジェクトを呼び出そうとするとエラーとなります。
:::

当たり前のことですが、僕はこれに引っかかってしまいました。

#### 具体例

Image のデータを親の Post の `id=1` を用いて作りたいとします。

DatabaseSeeder.php には、  
ImageSeeder の呼び出しの後に PostSeeder を呼び出しています。

```php:src/database/seeders/ImageSeeder.php
<?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:
<?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();
    }
}
```

実行してみると

```zsh:
php artisan migrate:fresh --seed
```

:::note alert
  Error
  Call to a member function getMorphClass() on null
:::

とエラーが出てしまいます。

これは、親（Post）がいないのに 子（Image）を作成しようとしているのでエラーが出ちゃいました。

**なので、子（Image）を作る前に親（Post）を作成しちゃえばエラーを解決できます。**

```diff:src/database/seeders/ImageSeeder.php
<?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 クラスがあるときは、記述順に注意が必要ですね。

:::note info
親がいないと子は作れない
:::

と覚えておくといいですね。

## さいごに

コードが肥大化して、メンテナンス性が落ちてしまうので、積極的にコードの分解を意識していきたいと思います。