【Laravel】 マイグレーションでテーブルの作成や編集をする

Laravelでは、マイグレーションを活用することでデータベースのテーブル構造をバージョン管理できます。

マイグレーションする

早速マイグレーションしてみましょう。

マイグレーションの作成

マイグレーションは、artisanを使用するのが楽です。

php artisan make:migration create_notes_table

実行すると、app/database/migrations/ディレクトリ内にファイルが作成されています。

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('notes');
    }
};

up()はマイグレーションを実行する際に実行される部分、downはマイグレーションをロールバックする際に実行される部分です。

create_xxyys_tableとすると、自動的にxxyysという名前のテーブル名で定義されます。そのため、ファイル名は必ずこの構造にしておきましょう。

マイグレーションの実行

先に下の内容でテーブル内容を変えてからの方が楽です。

先に走らせてしまっても、1つロールバックすればよいだけではあります。

マイグレーションを作成したら、それを実行してデータベースを更新します。

# 未処理の全てのマイグレーションを処理
php artisan migrate

# 破壊的変更がある際のyes noの選択を省略する場合
php artisan migrate --force

あとはデータベースを確認してテーブルが作成されていれば完了です。

その他のマイグレーションのコマンド

マイグレーションをロールバックするなどの操作もあります。開発環境ではmigrate:freshを、本番では--forceを付けるといったことはよくあると思います。

マイグレーションは、順番に実行するものです。そのため、特定のマイグレーションだけをロールバックしたり、実行したりといったことは行うべきではありません。

特定の1つのファイルをロールバックするのではなく、それに相当するマイグレーションを作成するべきです。

# どのマイグレーションを実行済みか表示
php artisan migrate:status

# 最後のマイグレーションをロールバック
php artisan migrate:rollback --step=1

# 最後から3つならstepを3に
php artisan migrate:rollback --step=3

# テーブル構造をダンプ (dumpのため、開発環境用)
php artisan schema:dump

# 全てのマイグレーションをロールバック (全部down)
php artisan migrate:reset

# 全てのマイグレーションをロールバックして再実行 (全部downして全部up)
php artisan migrate:refresh

# 指定個のマイグレーションをロールバックして再実行 (全部downして全部up)
php artisan migrate:refresh --step=1

# テーブルを全部削除してマイグレーションし直し (単純にテーブルを削除して全部up)
php artisan migrate:fresh

# 指定したマイグレーションだけ実行 (うまく行かない場合も)
php artisan migrate:refresh --step=1 --path=/database/migrations/ファイル名

テーブルの定義

マイグレーションファイルを作った直後ではidtimestampしかありません。ここにテーブル定義を追加していくことになります。

カラムの種類

例えば、最初からある$table->id()idはカラムの種類の1つで、AUTO_INCREMENTBIGINTで符号なしの主キーとなります。

また、ほとんどの種類で第1引数にはテーブルのカラム名が入ります。

// ...
return new class extends Migration
{
    public function up()
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id(); // 符号なしBigIntegerの主キー
            $table->timestamps(); // created_atとupdated_at

            $table->integer('hoge'); // 整数
            $table->string('content', 255); // 指定した長さのVARCHAR 第2引数を省略すると255になる
            $table->decimal('d', 8, 2); // 10進数の小数。第2引数は合計桁数、第3引数は小数の桁数。第4引数はunsignedかどうかのboolean
            $table->float('f', 8, 2, false);// 2進数の小数。引数は上と同じ
            $table->boolean('b'); // 真偽値
            $table->text('note_text'); // 文字列
            $table->longText('note_text2'); // 長い文字列
            $table->timestamp('last_access'); // タイムスタンプ
            $table->enum('content_type', ['text', 'html']); // 列挙型
        });
    }
};

https://readouble.com/laravel/9.x/ja/migrations.htmlの”利用可能なカラムタイプ”に種類と引数がすべて載っています。

カラム修飾子

カラム修飾子では、そのカラムの位置やコメント、nullを入れられるかどうかなどを設定できます。

https://readouble.com/laravel/9.x/ja/migrations.htmlの”カラム修飾子”に種類と引数がすべて載っています。

カラム修飾子は、メソッドチェーン形式で並べます。

// ...
return new class extends Migration
{
    public function up()
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id();
            $table->timestamps();

            $table->string('content')->nullable(false)->comment('そのnoteの本文');
        });
    }
};

メソッドチェーンは、a()->b("hoge")->c(10)->d()->e()のように、メソッドの戻り値がクラスインスタンスであることを利用し、メソッド呼び出しの戻り値を使ってメソッドを呼び出す形式です。

カラムの初期値

レコード挿入時に値の指定がなかった場合に入る値は、defaultメソッドを利用します。

このデフォルト値では、文字列や数値の場合は直接入れて問題ありませんが、nullCURRENT_TIMESTAMPなど、数値や文字以外の場合はExpressionを使用する必要があります。

->default(new Expression('CURRENT_TIMESTAMP'))のように、defaultメソッドの引数に入れます。

// ...

// useの追加を忘れないようにする
use Illuminate\Database\Query\Expression;

return new class extends Migration
{
    public function up()
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id(); // 主キー
            $table->timestamps(); // created_atとupdated_at

            // 文字列や数値、nullの場合は、直接値を入れるだけで良い
            $table->string('hoge')->default('hogehoge default text あいうえお');
            $table->integer('i')->default(10);
            $table->datetime('last_access')->nullable(true)->default(null);
            
            // 文字列以外の場合、new Expressionを使い、式として処理させる
            $table->dateTime('hoge_date')->default(new Expression('CURRENT_TIMESTAMP'));
        });
    }
};

データベースの制約上、BLOB、TEXT、GEOMETRY、JSONの4つは初期値を持つことができません。

テーブルの更新

テーブルの作成後にカラムを追加したり、削除したりといったことはよくあります。しかし、マイグレーションを書き換えるのはよくありません。マイグレーションのファイルはバージョンでもあり、そのマイグレーションを実行したかどうかも管理されています。

テーブルを変更したい場合、新しくマイグレーションファイルを作成して作業します。

どのマイグレーションの作成でもですが、ファイル名に_to_を使うと、それ以降がテーブル名と判定されます。そのため、テーブル名の直前以外で_to_は入れないようにするべきです。

カラムの追加と削除

カラムを追加する場合、作成時と同様に作りたいカラムを並べます。upにはマイグレーション時に実行したいものを、downにはロールバック時に実行したいものを書きます。

そのため、upでカラムを追加する場合、downにはカラムの削除を書くことが一般的です。

php artisan make:migration add_user_id_to_notes_table
public function up()
{
    Schema::table('notes', function (Blueprint $table) {
        // 符号なし整数型のversionカラムを作成
        $table->unsignedInteger('version');
    });
}

public function down()
{
    Schema::table('notes', function (Blueprint $table) {
        // versionカラムを削除
        $table->dropColumn('version');
    });
}

当然ですが、カラムを削除するとそこに入っているデータも消えます。

作成したら、php artisan migrateするとupが実行されます。

カラム名変更

カラム名変更の場合、マイグレーションの作成時はrename_xxxx_yyyy_to_tablename_tableのようにするのがおすすめです。

# rename_content_to_text_on_notes_table のようにすると、テーブル名がtext_on_notesだと判定される

php artisan make:migration rename_xxxx_yyyy_to_tablename_table

変更には、$table->renameColumn(from, to);のように書きます。

public function up()
{
    Schema::table('notes', function (Blueprint $table) {
        $table->renameColumn('content', 'text');
    });
}

public function down()
{
    Schema::table('notes', function (Blueprint $table) {
        $table->renameColumn('text', 'content');
    });
}

もしClass "Doctrine\DBAL\Driver\AbstractMySQLDriver" not foundのようなメッセージが出たら、composer require doctrine/dbalしてください。

カラムの型や属性変更

属性変更では、$table->新しい型('column name', ...)->...->change()のように書きます。

php artisan make:migration change_type_text_to_notes_table
public function up()
{
    Schema::table('notes', function (Blueprint $table) {
        $table->longText('text')->change();
        $table->unsignedInteger('version')->default(100)->change();
    });
}

public function down()
{
    Schema::table('notes', function (Blueprint $table) {
        $table->string('text')->change();
        $table->unsignedInteger('version')->default(0)->change();
    });
}

指定しなかった変更については変化しません。例えば、上の例でversionnull周りの設定が入っても変化することはありません。

外部キー

外部キーを利用し、テーブル同士を紐付けると言ったことはよくあります。マイグレーションでは、forginを使用します。

事前に適当に別のテーブルを作成しておきましょう。php artisan make:migration create_groups_table

注意点として、マイグレーションはファイル名順に実行されていくため、必ず親のテーブル (紐付けられる側) を先に作る必要があるという点です。先に親のテーブルを用意しないとテーブルが存在しない状態で外部キーを紐付けようとしてしまうため、失敗します。

もし順番を変えられない、変えたくない場合などでは、先にテーブルだけ作成しておいて、後から外部キーを定義することも可能です。

ファイル名の変更は、文字通りファイル名を変えるだけです。ファイル名の頭には日付がついているはずですので、その日付を適当に変えるだけです。

しかし、マイグレーション済みのファイルは、名前を変えるべきではありません。

外部キーを定義する際は、符号なし整数型 (つまり$table->id()で作成したもの) のカラム作成であればforeignIdを、それ以外の型や後から外部キーを付ける場合などはforeignを用います。

Schema::create('notes', function (Blueprint $table) {
    $table->id();
    $table->timestamps();

    // group_id というカラム(big integer unsigned)を作り、groupsテーブルのidと紐付ける
    $table->foreignId('gruop_id')
        ->references('id')
        ->on('groups');
});
Schema::create('notes', function (Blueprint $table) {
    $table->id();
    $table->timestamps();

    // まずカラムをつくる
    $table->unsignedBigInteger('group_id');

    // カラムに対して外部キーを定義する。 foreign(カラム名)->references(対象テーブルのカラム)->on(対象テーブル名);
    $table->foreign('group_id')
        ->references('id')
        ->on('groups');
});

外部キー制約

CASCADERESTRICTなどの外部キー制約を付与したい場合、メソッドチェーンでonUpdateonDeleteつなげます。

$table->foreign('group_id')
    ->references('id')
    ->on('groups')
    ->onUpdate('restrict')->onDelete('restrict');

外部キー制約は、以下の4種類があります

制約名UPDATEDELETE
CASCADE参照先を変更すると、参照側も変わる参照先が無くなると同時に参照側も削除される
RESTRICT参照先を変更しようとするとエラー参照先を削除しようとするとエラー
SET NULL参照先を変更するとNULLになる参照先を削除するとNULLになる
NO ACTIONRESTRICTと同じRESTRICTと同じ

SET NULLを使いたい場合、該当カラムがnullableである必要があります。

onUpdate('制約')の代わりに、cascadeOnUpdate()restrictOnUpdate()cascadeOnDelete()restrictOnDelete()nullOnDelete()でも同じことが可能です。

ここで、foreignIdにおけるカラム修飾子は、constrained()より前に呼ぶ必要があります。

$table->foreignId('group_id')
    ->nullable()
    ->constrained(); // constrained()->nullable() はNG

外部キーの削除

外部キーを削除する際は、$table->dropForeign(インデックス名);、または$table->dropForeign(['カラム名', ...])と書きます。

$table->dropForeign('notes_group_id_foreign');
$table->dropForeign(['group_id']);

ソフトデリート

データを削除する際は、本当に削除するハードデリートと、削除したという目印だけを付けてデータは残すソフトデリートの2つがあります。

もしソフトデリートしたい場合、$table->softDeletes();します。第1引数にはカラム名が入り、デフォルトはdeleted_atです。

public function up()
{
    Schema::table('notes', function (Blueprint $table) {
        $table->softDeletes();
    });
}

public function down()
{
    Schema::table('notes', function (Blueprint $table) {
        $table->dropSoftDeletes();
    });
}

まとめ

マイグレーション作成時はartisanを使用すると便利です。create_xxyy_tableadd_hoge_column_to_xxyy_tableなど、artisan実行時のファイル名はLaravelがテーブル名を検出できるようにするのが大事です。

どの作業も$tableからメソッドをつなぐと使えるので、入力補完なども駆使してテーブルを作ると良いです。

テーブルを作ったら、早速モデルを利用してデータのCRUDを見ていきましょう。

laravel migration thumb

役に立ったらシェアしよう!