はじめに

システムでマスターテーブル作ってマスターレコード登録ってよくありますよね。

  • migration
  • seeder
  • バッチ(artisanコマンド)

どれでやろうか悩んだ末、
今後はseederでやっていこうと心に誓ったのでそのメモ。

前提(やりたいこと)

  • 環境構築時はコマンド一発で終わりたい
  • 運用後のマスターテーブル、マスターレコードの変更を考慮する

今後の使い分け

  • migrate
    • テーブル作成、カラム変更など テーブルの操作のみ とする
    • レコード操作は行わない
  • seed
    • マスターレコード登録などシステムで 必要なレコードのみ とする ←今回
    • テストデータはseedで入れない
      • 各ユニットテストで入れればいいし、そのほうがわかりやすい
  • バッチ(artisanコマンド)
    • データ移行や補正など 運用上必要になったレコード操作 を対象とする

ベストプラクティス

データはCSVで管理

どんなデータがあるか把握しやすいようにプログラム内には書かず、ファイルで管理します(CSVでなくてもよい)

こんな感じ。

categories.csv

id,name,sort
1,カテゴリ1,1
2,カテゴリ2,2
3,カテゴリ3,3

運用後に追加、変更があったときはこのファイルを更新していく。

Seederを実装

テーブル毎にSeederを書きます。
マスターデータに変更が入る場合を考慮し、全delete->insertしてます。
※運用中は一瞬でもマスタレコード消えるとまいっちんぐマチコなのでトランザクション処理を忘れずに!

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use DB;
use App\Models\Category;

class CategoriesTableSeeder extends Seeder
{
    public function run()
    {
        $filePath = storage_path() . '/master_data/categories.csv';
        $records = read_csv_file($filePath); // 独自関数です。補足参照。
        DB::transaction(function () use ($records) {
            // 入れ替えるためまずは全レコード削除(テーブル名を直接書きたくないのでモデルを使用)
            Category::query()->delete();
            foreach ($records as $record) {
                // created_at, updated_atを自動で入れたいのでinsertではなくモデルでcreateしている。
                // NOTE: seederではfillableやguardedは無視されるので気にする必要なし。
                Category::create($record);
            }
        });
    }
}

実行(レコード登録)

クラスを指定してseedを実行します。CSVファイルに変更が入ったときもこちら。
php artisan db:seed --class=CategoriesTableSeeder

環境構築時

新メンバーがジョインした時は
php artisan migrate --seed
で一撃!

となるようにDatabaseSeeder.phpにcallする処理を入れておきましょう。

$this->call(CategoriesTableSeeder::class);

補足


    /**
     * CSVファイルを連想配列にして返す
     *
     * @param string $filePath
     * @return array
     */
    function read_csv_file(string $filePath): array
    {
        // ファイル存在チェック
        if(!\File::exists($filePath)) {
            throw new Exception('CSVファイルが見つかりませんでした。 filePath: '.$filePath);
        }

        $file = new \SplFileObject($filePath);
        $file->setFlags(
            \SplFileObject::READ_CSV
            | \SplFileObject::READ_AHEAD
            | \SplFileObject::SKIP_EMPTY
        );

        $key = [];
        $data = [];
        foreach ($file as $i => $row)
        {
            // 1行目を連想配列のキーとする
            if($i === 0) {
                foreach($row as $j => $value) {
                    $key[$j] = $value;
                }
                continue;
            }

            $line = [];
            foreach($row as $j => $value) {
                $line[$key[$j]] = $value;
            }
            $data[] = $line;
        }
        return $data;
    }

さいごに

各コマンドの使い分けも決めることができてスッキリしました!
それではステキなマスターレコードライフを✨