タイトル: マイグレーションとテーブル定義
SEOタイトル: Laravel マイグレーション完全ガイド — テーブル定義と運用
| この記事の要点 |
|
マイグレーションとは
マイグレーション (migration) はテーブル定義の変更を PHP コードで記述し、Git で管理する仕組みです。チーム開発で「自分の DB だけ古いカラムが残っている」事故を防げます。Laravel では database/migrations/ ディレクトリに保存されます。
マイグレーションファイル生成
# create テーブル用(推奨命名)
php artisan make:migration create_users_table
# 既存テーブル変更用
php artisan make:migration add_phone_to_users_table --table=users
# テーブル名を明示
php artisan make:migration create_articles_table --create=articles
# 生成されるファイル名
# database/migrations/2026_05_18_120000_create_users_table.php
ファイル名先頭のタイムスタンプが実行順序を決めます。一度コミットしたら基本的に変更せず、新しいマイグレーションを追加していきます。
基本のマイグレーションファイル
id(); // bigint UNSIGNED AUTO_INCREMENT PRIMARY KEY
$table->string('name', 100);
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps(); // created_at / updated_at
});
}
public function down(): void {
Schema::dropIfExists('users');
}
};
カラム型一覧
| メソッド | MySQL 型 | 用途 |
|---|---|---|
$table->id() | BIGINT UNSIGNED AUTO_INCREMENT | 主キー(推奨) |
$table->bigIncrements('id') | 同上 | id() の旧表記 |
$table->string('name', 255) | VARCHAR(255) | 短い文字列 |
$table->text('body') | TEXT | 長文 |
$table->longText('content') | LONGTEXT | 4GB まで |
$table->integer('count') | INT | 整数 |
$table->unsignedBigInteger('user_id') | BIGINT UNSIGNED | FK 用 |
$table->decimal('price', 10, 2) | DECIMAL(10,2) | 金額 |
$table->boolean('is_active') | TINYINT(1) | 真偽 |
$table->date('birthday') | DATE | 日付 |
$table->datetime('start_at') | DATETIME | 日時 |
$table->timestamp('created_at') | TIMESTAMP | タイムスタンプ |
$table->json('meta') | JSON | JSON カラム |
$table->enum('status', ['draft','published']) | ENUM | 列挙 |
$table->uuid('uuid') | CHAR(36) | UUID |
$table->timestamps() | created_at + updated_at | Eloquent 規約 |
$table->softDeletes() | deleted_at | 論理削除 |
カラム修飾子
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name', 100)
->nullable() // NULL 許可
->default('Anonymous') // デフォルト値
->comment('表示名'); // カラムコメント
$table->string('email')
->unique(); // UNIQUE 制約
$table->integer('age')
->unsigned() // UNSIGNED
->index(); // インデックス追加
$table->string('slug')
->after('name'); // 既存カラム直後に追加(alter 時)
$table->timestamps();
});
外部キー (foreignId / constrained)
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
// 推奨: foreignId + constrained
$table->foreignId('user_id')
->constrained() // users.id への FK
->onUpdate('cascade')
->onDelete('cascade');
// テーブル名が規約違反の場合
$table->foreignId('author_id')
->constrained('users') // 明示
->nullOnDelete(); // 削除時 NULL に
// 古典的書き方
$table->unsignedBigInteger('category_id');
$table->foreign('category_id')->references('id')->on('categories');
$table->timestamps();
});
インデックスとユニーク制約
Schema::create('logs', function (Blueprint $table) {
$table->id();
$table->string('user_id');
$table->string('action');
$table->timestamp('created_at')->useCurrent();
// 単一カラム
$table->index('user_id');
// 複合インデックス
$table->index(['user_id', 'action']);
// 名前指定
$table->index('action', 'idx_action_only');
// ユニーク
$table->unique(['user_id', 'action']);
// 全文検索 (MySQL)
$table->fullText('body');
});
// 既存テーブルに追加
Schema::table('users', function (Blueprint $table) {
$table->index('email');
});
// 削除
Schema::table('users', function (Blueprint $table) {
$table->dropIndex(['email']); // 自動命名規則
$table->dropIndex('idx_action_only'); // 明示名
$table->dropUnique(['user_id', 'action']);
});
既存テーブルの変更 (alter)
// カラム追加
Schema::table('users', function (Blueprint $table) {
$table->string('phone', 20)->nullable()->after('email');
});
// カラム削除
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('phone');
$table->dropColumn(['phone', 'address']); // 複数
});
// カラム変更(doctrine/dbal が必要、Laravel 11 以降は不要)
Schema::table('users', function (Blueprint $table) {
$table->string('name', 200)->change(); // VARCHAR(100) → VARCHAR(200)
$table->string('email')->nullable(false)->change();
});
// カラム名変更
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('name', 'full_name');
});
// テーブル名変更
Schema::rename('users', 'members');
マイグレーション実行コマンド
| コマンド | 動作 | 本番 |
|---|---|---|
php artisan migrate | 未適用の up() を順次実行 | ○ |
migrate:status | 適用状況を一覧表示 | ○ |
migrate:rollback | 直近のバッチを down() | △ |
migrate:rollback --step=3 | 3 ステップ戻す | △ |
migrate:refresh | 全 rollback + migrate | × |
migrate:reset | 全 rollback のみ | × |
migrate:fresh | 全テーブル DROP + migrate | 絶対 × |
migrate --pretend | SQL のみ表示(dry-run) | 確認用 |
migrate --force | 本番環境でも実行 (APP_ENV=production 時必須) | ○ |
# 開発時のサイクル
php artisan migrate:fresh --seed # 全消し → 再構築 → シーダ
# 本番デプロイ時
php artisan migrate --force # 確認なしで実行
php artisan migrate:status # 適用状況確認
# 特定パスのみ
php artisan migrate --path=database/migrations/2026_05_18_create_users_table.php
複数 DB 接続のマイグレーション
// config/database.php に複数接続を定義
// 'connections' => [
// 'mysql' => [...],
// 'mysql_logs' => [...],
// ]
return new class extends Migration {
protected $connection = 'mysql_logs'; // ← 接続指定
public function up(): void {
Schema::connection('mysql_logs')->create('access_logs', function (Blueprint $table) {
$table->id();
$table->string('url');
$table->timestamp('accessed_at');
});
}
};
スキーマダンプ (Laravel 8+)
マイグレーションファイルが大量になったらスキーマダンプで集約できます。
# 現状のスキーマを SQL でダンプ
php artisan schema:dump
# ダンプ後の古い migrate ファイルを削除
php artisan schema:dump --prune
# dump 後の挙動:
# - migrate:fresh は dump を一括実行 → 個別 migrate を流す
# - 大量のマイグレーションを高速に再現できる
本番運用のベストプラクティス
- down() を必ず書く。緊急ロールバック時に必要
- migrate:fresh は本番禁止。データ全消去される
--forceを CI でつける(APP_ENV=production だと確認プロンプトが出る)- 大規模 alter はpt-online-schema-change / gh-ost で実施(Laravel の標準は ALTER TABLE で行ロック)
- カラム削除はリリースを分ける: ①「アプリで参照しないように修正」→ デプロイ → ②「カラム DROP」マイグレーション
- マイグレーションファイルを過去日付に書き換えない(チームの DB と差分が出る)
FAQ
Q: マイグレーションファイルを間違って上書きしてしまった
A: migrations テーブルから該当行を削除して、再 migrate。チーム共有後なら新規ファイルで修正を。
Q: enum を後から変更したい
A: MySQL の ALTER TABLE で値追加自体は可能だが、Laravel 標準では対応薄。DB::statement("ALTER TABLE ...") で生 SQL を流す。
Q: マイグレーションで初期データを投入したい
A: マイグレーション内で DB::table()->insert() 可能だが、シーダ (Seeder) に分けるのが Laravel 流。