商売力開発ブログ

非エンジニアがWebサービスの開発、運営によって商売力をつける記録、その他の雑記

Laravelのマイグレーション機能の活用③ カラム定義の管理を楽にする方法

【スポンサーリンク】

今回はPHPフレームワークの一つのLaravelの中から、データベースのマイグレーションの機能の利用に当たって、我々の活用方法を紹介します。
今回利用するDBはMySQLですが、他のDBでもほぼ同様に対応できます。
またマイグレーションに関連する内容は以下を参照して下さい。

Laravelのデータベースのマイグレーション機能 順番を強引に入れ替えることも可能 - 商売力開発ブログ
Laravelのマイグレーション機能の活用① メンバ変数を追加した上でテーブルのコメント追加も簡単に設定する方法 - 商売力開発ブログ
Laravelのマイグレーション機能の活用② 1つのファイルで複数のテーブル管理も可能 - 商売力開発ブログ
Laravelのマイグレーション機能の活用④ カラム定義のメソッド設定例 - 商売力開発ブログ
Laravelのマイグレーション機能の活用⑤ 複数テーブルに同じカラムを追加する - 商売力開発ブログ

テーブルの物理設計のときにカラム定義は確定できてるか?

テーブルを物理設計する際には、テーブル名やカラム名とその属性を定義しなければなりません。当たり前ですね。ただこの定義って皆さん最初から確定できてるんでしょうか。
名前に関しては命名規約があれば、それに従って決まるものとして、今回問題として考えるのはカラムの属性です。特に新規のサービス開発しているときなどは、途中でカラムの属性を変更することがあります。このカラムの属性の管理を簡単にできないかLaravelのマイグレレーションで考えてみたいと思います。まずは以下のマイグレーションファイルを見てましょう。

class CreateSamplesTable extends Migration
{
    public $tbl_name='samples';//テーブル名のプロパティ追加
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create($this->tbl_name, function (Blueprint $table) {
            $table->increments('id');
            $table->string('name',255);
            $table->string('group_cd',5)->nullable();
            $table->smallInteger('amount');
            $table->double('rate', 5, 2);
        });
        // add comments
        DB::statement("ALTER TABLE ".DB::getTablePrefix().$this->tbl_name." COMMENT 'サンプルテーブル'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists($this->tbl_name);
    }
}

このテーブルでは5つのカラムが定義されています。ここで各カラムの定義の変更が必要になった場合、通常Laravelではカラム変更用のマイグレーションファイルを作成して対応することになります。
新規サービス開発のときなど何度も変更がされるような開発状況の場合、ファイルが多くなっていって結構面倒です。また同じカラムを持つ他のテーブルについてもマイグレーションファイルを作成することになりますので、ファイルは更に多くなります。一つの変更用のマイグレーションファイルで複数のテーブルに対応することも可能ではあります。ただ途中で新たに追加されたテーブルが出てきて、その後にカラムの変更する際には対象から外れてしまったりと、色々と事故の要因になります。このように初期開発時などテーブルの物理設計がどんどん変更されても、あまり面倒にならないように対応方法を考えてみたいと思います。
※サービスが開始している場合は標準の変更用のマイグレーションファイルを作成しての対応を推奨します。

どのように対応していくか

まず我々が最初に行っていたのはテーブルの定義が変更となる度に、そのテーブルを持つマイグレーションファイルをロールバックして、修正し再度マイグレーションする方法です。以前の内容でも触れましたが、以下のSQLで取得できるbatchの値を変更することでロールバックの対象にする方法です。

Select * From migrations
Order By batch desc,id;

batchの値を最大値にupdateすることで、マイグレーションの順番を強引に入れ替えて強制的にロールバックの対象となります。例えばSQLで取得した結果が以下の状態だっとします。

id migration batch
1 2018_01_01_000000_create_users_table 1
2 2018_01_01_000000_create_password_resets_table 1
3 2018_01_02_000000_create_samples_table 1
4 2018_01_03_000000_create_others_table 1

id:3の「2017_12_31_000000_create_samples_table」が変更対象のテーブルのとき、以下のようにbatchの値を更新します。

id migration batch
1 2018_01_01_000000_create_users_table 1
2 2018_01_01_000000_create_password_resets_table 1
3 2018_01_02_000000_create_samples_table 2
4 2018_01_03_000000_create_others_table 1

この状態でロールバックした後のSQLでの取得結果は以下のようになります。

id migration batch
1 2018_01_01_000000_create_users_table 1
2 2018_01_01_000000_create_password_resets_table 1
4 2018_01_03_000000_create_others_table 1

マイグレーションファイルに必要な修正を行います。ファイル名称は変更していません。その後、マイグレーションを実行するとSQLでの取得結果は以下のようになります。

id migration batch
5 2018_01_02_000000_create_samples_table 2
1 2018_01_01_000000_create_users_table 1
2 2018_01_01_000000_create_password_resets_table 1
4 2018_01_03_000000_create_others_table 1

この対応で特定のテーブルだけを再作成し、列定義の変更する度にマイグレーションファイルが増加させずに済ませることができます。
この方法の大きな問題点は複数環境がある場合です。(大抵の場合、当てはまりそうですが・・・)
強引にロールバックの対象を変更する作業を行っていますが、他の環境にこの修正後のマイグレーションを適用するには同じ作業が必要となってしまいます。この辺りを運用方法や回避策についてはまた別のエントリーで書きたいと思います。

カラム定義の共通化

ここからはカラム定義の管理の仕方を考えます。以下のupメソッドがある2つのテーブルがあるとします。
1つ目のテーブルは上で出てきたサンプルテーブルです。

    public $tbl_name='samples';//テーブル名のプロパティ追加
    public function up()
    {
        Schema::create($this->tbl_name, function (Blueprint $table) {
            $table->increments('id');
            $table->string('name',255);
            $table->string('group_cd',5)->nullable();
            $table->smallInteger('amount');
            $table->double('rate', 5, 2);
        });
        // add comments
        DB::statement("ALTER TABLE ".DB::getTablePrefix().$this->tbl_name." COMMENT 'サンプルテーブル'");
    }

2つ目のテーブルはグループテーブルです。1つ目のサンプルテーブルの「group_cd」にはこのグループテーブルに存在する「group_cd」が登録されるものとします。

    public $tbl_name='groups';//テーブル名のプロパティ追加
    public function up()
    {
        Schema::create($this->tbl_name, function (Blueprint $table) {
            $table->increments('id');
            $table->string('group_cd',5);
            $table->string('name',255);
        });
        // add comments
        DB::statement("ALTER TABLE ".DB::getTablePrefix().$this->tbl_name." COMMENT 'グループテーブル'");
    }

ここで「group_cd」は5桁の文字列型(MySQLではVARCHARで作成されます)となっています。この「group_cd」の桁数や型を変更することになった場合、これら2つのマイグレーションファイルを変更する必要が出てきます。このカラムがもっと多くのテーブルに使用されている場合、変更するだけでも結構な作業量になるのでどうにかして、効率化したいと思います。
といってもやることは簡単で、カラムの定義を関数で行ってしまおうというものです。

$table->string('group_cd',5);

としている部分を、例えば

columnDef('group_cd');

というようにカラム名を渡すと共通の定義ができるようにするイメージです。我々の場合、Laravelのファサード(仮にMigrationFuncとします)を作成しています。またBlueprintオブジェクトの$tableにカラムの定義をすることになるので$tableを渡す必要があります。こんな感じです。

MigrationFunc::columnDef($table,'group_cd');

このMigrationFuncファサードのメソッドによって、「$table->string('group_cd',5);」が実行されるように実装しています。ただ2つのupメソッドの設定を見れば分かる通り、テーブルによってはnullableにできる必要があります。MigrationFuncファサードのメソッドの引数の定義は以下のようになります。(実装部分については省略します⇒こちらに記載しました)

public function columnDef(Blueprint $table,$column_name,$nullable_flg=false){}

カラムの共通定義は連想配列などで以下のような感じで、MigrationFuncファサード内に設定しておきます。

$column_def = [
    'id' => ['type'=>'increments','comment'=>'ID'],
    'name' => ['type'=>'string','length'=>255,'comment'=>'名前'],
    'group_cd' => ['type'=>'string','length'=>5,'comment'=>'グループCD'],
];

渡ってきた$colNameから、カラムの共通定義の設定を$tableに行い、必要に応じて追加される設定(この場合はnullable)を行うようにします。このメソッドをマイグレーションファイルに設定します。
1つ目のサンプルテーブル

    public $tbl_name='samples';//テーブル名のプロパティ追加
    public function up()
    {
        Schema::create($this->tbl_name, function (Blueprint $table) {
            MigrationFunc::columnDef($table,'id');
            MigrationFunc::columnDef($table,'name');
            MigrationFunc::columnDef($table,'group_cd',true);
            $table->smallInteger('amount');
            $table->double('rate', 5, 2);
        });
        // add comments
        DB::statement("ALTER TABLE ".DB::getTablePrefix().$this->tbl_name." COMMENT 'サンプルテーブル'");
    }

2つ目のグループテーブル

    public $tbl_name='groups';//テーブル名のプロパティ追加
    public function up()
    {
        Schema::create($this->tbl_name, function (Blueprint $table) {
            MigrationFunc::columnDef($table,'id');
            MigrationFunc::columnDef($table,'group_cd');
            MigrationFunc::columnDef($table,'name');
        });
        // add comments
        DB::statement("ALTER TABLE ".DB::getTablePrefix().$this->tbl_name." COMMENT 'グループテーブル'");
    }

このようにしておくことで特定のカラムで変更があった場合、カラムの共通定義の設定を変更し、対象となるマイグレーションファイルをロールバックしてマイグレーションをすることで変更が反映されます。このときマイグレーションファイル自体には何の修正もする必要がありません。共通定義から同じ設定が対象のカラムにされることになります。サンプルテーブルでは一部のカラムの定義は$tableのままになっています。複数のテーブルで使用しないカラムは通常の設定で行っても良いかもしれません。
この方法の問題点はあるカラムの変更がある場合、そのカラムを使用しているマイグレーションファイルを洗い出して、ロールバックできるようにする作業が必要になることです。こちらを参考にして、カラム名を指定してSQLを流せば、使用しているテーブルを確認することはできます。

Select * From information_schema.columns I1
Where I1.table_schema = 'DB_NAME'
And I1.column_name='COLUMN_NAME';

ただし、 MigrationFunc::columnDefではなく標準の設定方法で定義したカラムも同じカラム名であれば取得されることになりますので注意して下さい。MySQLの場合、カラムに対してコメントの設定が可能です。 我々の場合、MigrationFunc::columnDefで設定したカラムは特定のコメントが付くようにして、標準の設定方法のカラムと区別するようにしてます。

まとめ

今回はLaravelのマイグレーションの我々の活用方法のうちカラムの管理方法を紹介しました。
新規サービスの開発段階では一部は使える方法ではないかと思いますので、参考にしてみて下さい。

以上

【関連するリンク】 www.prj-alpha.biz www.prj-alpha.biz www.prj-alpha.biz www.prj-alpha.biz www.prj-alpha.biz

【スポンサーリンク】