LoginSignup
9
2

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-06-29

はじめに

先日、Laravel でポリモフィック関連の Factory を作成してみた という記事を書きました。

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

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

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

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

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

  • 肥大化した DatabaseSeeder.php を分解できる。
  • 分解したクラス(Seeder クラス)を呼び出すことができる。

肥大化した DabaseSeeder.php の確認

DatabaseSeeder のコード
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();
    }
}

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

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

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

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

やりたいこと

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

0. 実装の流れ

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

最終的なDatabaseSeeder.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 ファイルを下記のコマンドで作成します。

php artisan make:seeder 〇〇〇Seeder

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

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

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

ImageSeeder

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

PostSeede.php のコード
※やることは ImageSeeder と同じなので折りたたんでいます。興味あれば見てみて下さいm(_ _)m。
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();
    }
}

CommentSeeder

CommentSeeder.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();
    }
}

UserSeeder

UserSeeder.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();
    }
}

VideoSeeder

CommentSeeder.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();
    }
}

DatabaseSeeder

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

DarabseSeeder.php のコード
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();
    }
}

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

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

記述方法は簡単で、

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

これだけです。

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 クラスの記述順に注意が必要です。

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

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

具体例

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

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

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 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)を作成しちゃえばエラーを解決できます。

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 クラスがあるときは、記述順に注意が必要ですね。

親がいないと子は作れない

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

さいごに

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

9
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2