この記事では Laravel におけるマイグレーションのしくみについて説明します。マイグレーションファイルの書き方やマイグレーションコマンドのパラメータなど各論はドキュメント(日本語)に書かれている通りですので、いかにマイグレーションが実現されているかを紹介したいと思います。
マイグレーションとは?
DB マイグレーションとは、テーブル定義を管理する仕組みのことです。
アプリケーションを開発するにあたって、テーブル定義はしばしば変更されるものです。初期フェーズの開発中においてさえテーブルやカラムの追加は発生しますし、リリースした後もアプリケーションの成長(=機能追加、仕様変更)に合わせてテーブル定義も当然変化します。
そこで、変遷するテーブル定義を管理する必要が出てきます。DB マイグレーションツールはどのような SQL をどの順番で実行したかを管理します。実行される SQL 文はアプリケーションコードとともにバージョン管理されます。
ある環境にアプリケーションをリリースするときや新しいメンバーを迎えるとき、このようにテーブル定義の管理がされていると、データベースをあるべき状態に再現することが容易になります。
Laravel におけるマイグレーション
Laravel におけるマイグレーションは以下のように実現されます。
- 一意な名前のマイグレーションファイル(テーブル定義に対する変更内容)を作成する。マイグレーションファイルは
up
とdown
という2つのメソッドを持つ PHP クラスとして記述される。 - 「どのような SQL をどの順番で実行したか」は
migrations
テーブルで管理される。migrations テーブルには実行済みのマイグレーションファイル名が格納されている。 - マイグレーションコマンドを実行すると、まず
migrations
テーブルの検索結果とマイグレーションファイルが入っているディレクトリを比べて、実行されていないファイルが検索される。 - 実行されていないファイルがあった場合は、順番に
up
メソッドが実行される。
ではここから実演形式で見ていきたいと思います。
マイグレーションファイルを作成する
タスク管理アプリのためのこのような Todos テーブルを作成したいとしましょう。
列名 | 型 | PK | NOT NULL | DEFAULT |
---|---|---|---|---|
id | serial | |||
title | varchar(100) | |||
description | text | |||
due_date | datetime | |||
completed | int | 1 |
以下のコマンドでまずはマイグレーションファイルを作成します。
$ php artisan make:migration create_todos_table --create=todos
マイグレーションファイルとは、テーブル定義に対する変更内容が書かれるファイルです。SQL そのものである場合もありますが、Laravel では PHP によるプログラムコードで表現されます。
先のコマンドで 2018_10_22_175338_create_todos_table.php
という雛形ファイルが database/migrations
ディレクトリに作成されたでしょう。ファイル名の数字の部分はファイルが作成された年月日時分秒ですので、実行したタイミングで異なります。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTodosTable extends Migration
{
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('todos');
}
}
Laravel のマイグレーションファイルは Migration
クラスを継承したクラスとして表現され、up
と down
のふたつのメソッドを持ちます。マイグレーション実行時は up
メソッドが実行されますので、まずは up
メソッドを編集しましょう。
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->increments('id');
$table->string('title', 100);
$table->text('description')->nullable();
$table->dateTime('due_date');
$table->integer('completed')->default(1);
$table->timestamps();
});
}
Schema::create()
でテーブルを作成します。第一引数がテーブル名、第二引数の無名関数がカラム定義です。カラム型定義のバリエーションなどはマニュアルを参照してください。
ちなみに、後述しますが down
メソッドはロールバック(マイグレーションを元に戻す機能)の時に実行されます。
マイグレーションを実行する
続いて artisan コマンドでマイグレーションを実行しましょう。ここで初めてデータベースに対して DDL が発行されます。
$ php artisan migrate
Migrating: 2018_10_22_181152_create_todos_table
Migrated: 2018_10_22_181152_create_todos_table
コマンドが正常に実行できると、マイグレーションで指定したテーブルの他に、migrations
というテーブルが作成されています。データベースクライアントソフトなどで中身を確認してみましょう。
id | migration | batch |
---|---|---|
1 | 2018_10_22_175338_create_todos_table | 1 |
このように、実行済みのマイグレーションファイルの名前が登録されています。そのため、もう一度マイグレーションコマンドを実行しても、何も起きません。
$ php artisan migrate
Nothing to migrate.
migrations
テーブルに問い合わせを行なって、database/migrations
ディレクトリの中には未実行のファイルはないと分かると何もしないのです。
マイグレーションファイルさえバージョン管理によって同期が取れていれば、誰がどの段階から実行しても結果は同じになるということです。
また注意点として、実行されているかどうかはテーブルで管理されているため、あるマイグレーションファイルを実行したあとに間違いに気づいて、ファイルを修正してもう一度実行しても何も起きません。間違えてしまった場合は基本的にロールバックしましょう。
複数のマイグレーションファイル
さて、複数のマイグレーションファイルを実行するとどうなるかを実演するために、タスク管理アプリにカテゴリーを追加すると仮定しましょう。
Categories テーブル(テーブル追加)
列名 | 型 | PK | NOT NULL | DEFAULT |
---|---|---|---|---|
id | serial | |||
name | varchar(30) |
Todos テーブル(カラム追加)
列名 | 型 | PK | NOT NULL | DEFAULT |
---|---|---|---|---|
category | int |
今回はふたつのマイグレーションファイルを作成します。
$ php artisan make:migration create_caterogies_table --create=categories
$ php artisan make:migration add_category_to_todos --table=todos
マイグレーションファイルの分け方に決まりはありませんが、少なくとも機能ごと・テーブルごとに分けると管理しやすいでしょう。
マイグレーションファイルの内容は、それぞれ以下の通りです。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCaterogiesTable extends Migration
{
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 30);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('categories');
}
}
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCategoryToTodos extends Migration
{
public function up()
{
Schema::table('todos', function (Blueprint $table) {
$table->integer('category');
});
}
public function down()
{
Schema::table('todos', function (Blueprint $table) {
$table->dropColumn('category');
});
}
}
コマンドでマイグレーションを実行します。
$ php artisan migrate
Migrating: 2018_10_22_185431_create_caterogies_table
Migrated: 2018_10_22_185431_create_caterogies_table
Migrating: 2018_10_22_185618_add_category_to_todos
Migrated: 2018_10_22_185618_add_category_to_todos
今回は未実行のファイルが2つありますので、それぞれファイル名順=作成日順(ファイル名の冒頭が作成日時なので)に実行されます。migrations
テーブルはどうなっているでしょうか。
id | migration | batch |
---|---|---|
1 | 2018_10_22_175338_create_todos_table | 1 |
2 | 2018_10_22_185431_create_caterogies_table | 2 |
3 | 2018_10_22_185618_add_category_to_todos | 2 |
実行した2行が追加されています。実行順に ID が振られている他に、batch というカラムに注目してください。batch カラムは一回のコマンドで実行したマイグレーションのまとまりを記録しています。先ほど2つのファイルを実行したので、どちらにも「2」が登録されました。
batch カラムは、ここから説明するロールバックの際に参照されます。
ロールバック
ロールバックはマイグレーションの内容を逆に元に戻す機能です。とは言っても自動的に元に戻す処理が実行される訳ではありません。より正確にいうと、ロールバックは直近のマイグレーションファイルの down
メソッドを実行するコマンドです。
ロールバックコマンドを実行してみましょう。
$ php artisan migrate:rollback
Rolling back: 2018_10_22_185618_add_category_to_todos
Rolled back: 2018_10_22_185618_add_category_to_todos
Rolling back: 2018_10_22_185431_create_caterogies_table
Rolled back: 2018_10_22_185431_create_caterogies_table
先ほど実行した2つのマイグレーションがロールバックされたようです。このようにロールバックは batch 単位で実行されます。
マイグレーションは機能ごとにまとめることが多いです。機能ごとにバージョン管理のコミットを分けるのと同じです(文字通りマイグレーションファイルもバージョン管理するわけですし)。そのためロールバックで元に戻す際には1行ずつ戻すと逆に中途半端な状態になるので、batch 単位で元に戻す挙動がデフォルトになっています。
さて、migrations
テーブルを見てみましょう。
id | migration | batch |
---|---|---|
1 | 2018_10_22_175338_create_todos_table | 1 |
ロールバックされた直近の batch のマイグレーションがテーブルから削除されています。ファイルの内容を修正してもう一度できる状態です。down
メソッドもきちんと書いておくと間違えた時も対処しやすいですね。
以上、この記事では Laravel におけるマイグレーションのしくみを説明しました。