Laravel Migrationの使い方完全ガイド|作成・実行・ロールバックとよくあるエラー対処

Laravel入門

「Laravel Migration」と検索する人の多くは、マイグレーションとは何かという概念だけでなく、実際に手を動かして「テーブルを作る」「カラムを追加する」「間違えたら戻す」までの一連の操作をひと通り知りたいはずです。この記事では、マイグレーションの基本概念から、現行のLaravelで使われる書き方、よく使うカラム型・外部キー制約、実行時につまずきやすいエラーの対処法までを、実際にコマンドを実行して動作確認した内容で解説します。

Laravel Migrationとは?

マイグレーション(Migration)は、データベースのスキーマ(テーブル構造)をPHPコードでバージョン管理する仕組みです。SQLを直接実行する代わりに、テーブルの作成・変更をコードとしてdatabase/migrationsディレクトリに残すことで、次のようなメリットが得られます。

  • スキーマの変更履歴をGitなどのバージョン管理システムで追跡できる
  • チーム開発で、他のメンバーが行ったテーブル変更をphp artisan migrate一発で自分の環境に反映できる
  • 本番環境へのデプロイ時に、同じマイグレーションを実行するだけでスキーマを同期できる

つまりマイグレーションは「テーブル定義のGit管理」と考えると理解しやすくなります。

マイグレーションファイルの作成

マイグレーションファイルはmake:migrationコマンドで作成します。

php artisan make:migration create_posts_table

実行すると、database/migrations/2026_07_21_021631_create_posts_table.phpのようにタイムスタンプ付きのファイル名で作成されます。このタイムスタンプがそのまま実行順序になるため、ファイル名を後から変更するとマイグレーションの順序がずれる原因になります。make:migrationコマンドのオプションについてはmake:migration — マイグレーションファイルを作成するコマンドで詳しく解説しています。

既存テーブルにカラムを追加するなど、新規作成以外のマイグレーションを作る場合は--tableオプションでテーブル名を指定すると、雛形にSchema::table()が自動生成されます。

php artisan make:migration add_published_at_to_posts_table --table=posts

マイグレーションファイルの書き方

Laravel 9以降で作成したマイグレーションファイルは、クラス名を持たない匿名クラス(return new class extends Migration)で生成されます。ネット上の古い記事ではclass CreatePostsTable extends Migrationのようなクラス名ありの書き方をよく見かけますが、現行バージョンではこの匿名クラス構文が標準です。

<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->text('body');
            $table->string('status')->default('draft')->index();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

up()にはマイグレーション実行時の処理、down()にはロールバック時に元に戻す処理を書きます。上記の例では次のポイントを押さえています。

  • foreignId('user_id')->constrained()->cascadeOnDelete()usersテーブルへの外部キー制約を張り、親レコード削除時に関連するpostsも自動削除する
  • ->index()statusカラムに絞り込み検索用のインデックスを付与する
  • ->default('draft'):カラムのデフォルト値を指定する

この状態でphp artisan migrateを実行すると、外部キー制約・インデックス付きでpostsテーブルが作成されることを実機で確認済みです。

よく使うカラム型・修飾子

Schema Builderで使うカラム型と修飾子のうち、実務でよく使うものを一覧にまとめました。

メソッド 用途
$table->id() 自動増分の主キー(bigint unsigned
$table->string('name', 100) VARCHAR型。第2引数で最大文字数を指定
$table->text('body') 長文用のTEXT型
$table->integer() / unsignedInteger() 整数値。unsignedで負数を禁止
$table->boolean('is_active') 真偽値
$table->foreignId('user_id')->constrained() 外部キー制約付きのID列
$table->timestamps() created_at / updated_atを自動生成
$table->softDeletes() 論理削除用のdeleted_atを追加
->nullable() NULLを許可する修飾子
->default(値) デフォルト値を設定する修飾子
->unique() / ->index() ユニーク制約 / インデックスを付与
->after('カラム名') 既存テーブルへのカラム追加時に挿入位置を指定

マイグレーションの実行

作成したマイグレーションをデータベースに反映するにはmigrateコマンドを実行します。

php artisan migrate

実行済みのマイグレーションはmigrationsテーブルにバッチ番号とともに記録され、二度目以降のmigrate実行では未実行分だけが適用されます。コマンドの詳細なオプション(--force--pretendなど)はmigrate コマンド — DBマイグレーションを未適用分から順に実行するを参照してください。本番環境では確認プロンプトを省略するため--forceオプションが必須です。

ロールバック・リセット・リフレッシュの違い

マイグレーションを取り消す・やり直すコマンドは複数あり、用途によって使い分けます。

コマンド 動作
migrate:rollback 直近のバッチ(またはstepで指定した回数分)だけ取り消す
migrate:reset 実行済みの全マイグレーションを取り消す
migrate:refresh 全マイグレーションをロールバックしてから再実行する
migrate:fresh 全テーブルを直接ドロップしてから再実行する(down()を経由しない)
php artisan migrate:rollback --step=2

各コマンドの詳細と実行例はmigrate:reset — マイグレーションをリセットするコマンドmigrate:refresh — マイグレーションをすべてリフレッシュして再実行するコマンドで個別に解説しています。

よくあるエラーと対処法

SQLSTATE[HY000]: table “xxx” already exists

同名のテーブルを作るSchema::createを含むマイグレーションを、テーブルが既に存在する状態で実行すると発生します。実際に検証すると次のようなエラーになります。

SQLSTATE[HY000]: General error: 1 table "posts" already exists

原因のほとんどは「同じテーブルを作るマイグレーションファイルが重複している」か「一度migrateした後に手動でテーブルを削除せず再実行した」ケースです。Schema::hasTable()で分岐する対症療法ではなく、重複したマイグレーションファイルを整理する、あるいはmigrate:freshでテーブルごと作り直すのが根本的な解決になります。

Nothing to migrate.

実行すべき未適用のマイグレーションが無い場合に表示されます。エラーではなく正常な状態なので、変更が反映されない場合はまずmigrate:statusで対象のファイルが「Pending」になっているか確認してください。ファイル名(タイムスタンプ部分)を後から変更すると、既存の実行履歴と噛み合わなくなりこの状態になることがあります。

ロールバックでカラムが消せない

down()dropColumn()の記述漏れがあると、up()で追加したカラムがロールバック後も残ります。カラム追加系のマイグレーションでは、up()と対になるdown()を必ず書くことを習慣にしてください。

実行状況の確認とシーディングとの連携

どのマイグレーションが適用済みかはmigrate:statusで一覧表示できます。

php artisan migrate:status

コマンドの出力の見方はmigrate:status — マイグレーションの状態を確認するコマンドで解説しています。また、テーブル作成後にテスト用データを投入する場合はdb:seedと組み合わせるのが定番です。

php artisan migrate:fresh --seed

--seedオプションを付けると、テーブル再作成後に自動でSeederが実行されます。Seederの書き方はdb:seed — データベースにシードデータを投入するコマンド、テーブル単体の雛形作成はdb:table — テーブル用のマイグレーションファイルを作成するを参照してください。

よくある質問

マイグレーションとシーダーの違いは?

マイグレーションはテーブルの「構造」を管理し、シーダーはテーブルに投入する「データ」を管理します。役割が異なるため、テーブル定義の変更はマイグレーション、テストデータの投入はシーダーと使い分けます。

本番環境でマイグレーションを実行する際の注意点は?

本番環境ではphp artisan migrate --forceのように--forceを付けないと確認プロンプトで止まります。また、大量データが入ったテーブルへのカラム追加はロックによる影響が出ることがあるため、事前にステージング環境で実行時間を確認しておくと安全です。

また、適切なインデックス($table->index())設定は検索性能だけでなく、同時更新時の行ロック(lockForUpdate)でテーブル全体がロックされるのを防ぐためにも極めて重要です。排他ロックを用いたデータ競合防止の実装については、Laravel lockForUpdate()の使い方|排他ロック(悲観的ロック)とトランザクションの実装・注意点 もあわせてご覧ください。

マイグレーションファイルを後から書き換えてもいい?

既に本番環境やチームメンバーの環境で実行済みのマイグレーションファイルを書き換えるのは避けてください。実行履歴(migrationsテーブル)とファイルの内容がずれ、環境ごとにスキーマが食い違う原因になります。変更したい場合は、修正用の新しいマイグレーションファイルを追加します。

テーブル名やカラム名を後から変更するには?

Schema::rename()$table->renameColumn()を使った新しいマイグレーションを作成します。カラムのリネームにはdoctrine/dbalパッケージが必要になるLaravelのバージョンもあるため、実行前にエラーが出た場合はパッケージの導入状況を確認してください。

まとめ

Laravelのマイグレーションは、テーブル定義をコードとして管理し、チームや環境間でスキーマを一致させるための仕組みです。make:migrationで雛形を作り、up() / down()に処理を書き、migrateで反映、間違えたらrollbackfreshで戻す、という一連の流れを押さえておけば、テーブル作成からカラム追加・外部キー制約まで一通り対応できます。個別のArtisanコマンドの詳細は、それぞれのコマンドリファレンス記事もあわせて参照してください。

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント