メシのタネ

メシのタネになる、Laravelや設計思想の技術配信サイト


Laravel マイグレーション超入門


  1. Laravel
  2. Laravel マイグレーション超入門

Laravel で初めてデータベースを触る人でも、「マイグレーションって何を、どうやって、なぜやるのか」 が最短で腹落ちすることを目指したガイドです。この記事を読み終えるころには、テーブル作成から安全なスキーマ変更、テストへの組み込みまで “ひととおり回せる” スキルセットが身につきます。

はじめに ― Laravelのmigrationってなにするの?

Laravel のマイグレーションは 「DB スキーマを PHP クラスでバージョン管理する仕組み」
git push と同じ感覚で php artisan migrate を叩けば、コードとデータベースの状態が常に揃う。SQL を手書きするより 事故率が下がる理由 は3つだけ覚えれば OK。

  1. 差分管理 ― 変更履歴が Git に残る
  2. 自動ロールバックdown() を書けばワンコマンドで巻き戻し
  3. テストとの親和性 ― テスト前に migrate:fresh すれば毎回クリーンな DB

3 行で動く最小サンプル

php artisan make:migration create_posts_table
php artisan migrate
php artisan migrate:rollback

マイグレーションの作法 ― コマンドを「使う順」で覚える

前章の 3 行サンプルposts テーブルを作ったあなたが、次に踏むべきステップを「作る → 試す → 壊す → また作る」の順でまとめる。コマンド名を丸暗記するより ストーリー で覚えるとミスが減る。

作成 — 雛形を切る

変更用マイグレーションのクラスファイルを生成する。

# posts テーブルに status カラムを追加する雛形
php artisan make:migration add_status_to_posts --table=posts

適用 / 戻す — 反映・検証

マイグレーションを実際に流してアプリを動かし、問題があればすぐ巻き戻す。

# 変更を適用
php artisan migrate

# 直前のマイグレーションを 1 つ戻す
php artisan migrate:rollback

Tip: --pretend フラグを付ければ、実行される SQL だけを表示して安全確認できる。

作り直し — リセット / 一括実行

ローカル環境や CI で DB をまっさら状態から再構築したいときに便利。

# 全マイグレーションをやり直し & seeder を流す
php artisan migrate:refresh --seed

# すべての down() を実行して初期状態へ戻す
php artisan migrate:reset

マイグレーションは コードの一部。小さく回して「作る → 試す → 壊す」を高速で繰り返せば、想定外のバグも rollback と Git で即リカバーできる。

Migration コマンド早見表(チートシート)

# ── 初期化 ────────────────────────────────
php artisan migrate:install                              # migrations テーブルを作成

# ── 作成 ─────────────────────────────────
php artisan make:migration create_tasks_table --create=tasks   # 雛形を生成

# ── 適用/巻き戻し ───────────────────────
php artisan migrate --force                                   # 変更を適用(本番では --force 必須)
php artisan migrate:rollback --step=1                         # 直近 1 本だけ巻き戻す

# ── まとめて戻す/やり直す ──────────────────
php artisan migrate:reset                                     # down() をすべて実行
php artisan migrate:refresh --seed                            # reset → migrate を一括実行
php artisan migrate:fresh --seed                              # テーブル DROP → migrate し直し

# ── 状態確認 ───────────────────────────────
php artisan migrate:status                                    # 実行/未実行を一覧表示

# ── スキーマを SQL ダンプ ───────────────────
php artisan schema:dump --prune                               # dump 後に古いファイルを削除
```bash
# ── 作成 ─────────────────────────────────
php artisan make:migration create_tasks_table --create=tasks   # 雛形を生成

# ── 適用/巻き戻し ───────────────────────
php artisan migrate --force                                   # 変更を適用(本番では --force 必須)
php artisan migrate:rollback --step=1                         # 直近 1 本だけ巻き戻す

# ── まとめて戻す/やり直す ──────────────────
php artisan migrate:reset                                     # down() をすべて実行
php artisan migrate:refresh --seed                            # reset → migrate を一括実行
php artisan migrate:fresh --seed                              # テーブル DROP → migrate し直し

# ── 状態確認 ───────────────────────────────
php artisan migrate:status                                    # 実行/未実行を一覧表示

# ── スキーマを SQL ダンプ ───────────────────
php artisan schema:dump --prune                               # dump 後に古いファイルを削除

スキーマ定義のツボ ― 型・インデックス早見表 + 失敗例

カラム属性早見表

メソッド / 型例目的一言メモ
bigIncrements('id')符号なし 64‑bit 主キーレプリケーション時に衝突しづらい
uuid('id')UUID 主キーマルチ DB で被りにくい
string('title', 150)可変長文字列191 文字以下なら古い MySQL でも index 可能
text('body')長文インデックス不可。全文検索は別途 fulltext()
boolean('is_published')->default(false)真偽値 + デフォルトTINYINT(1) 扱いなので注意
decimal('price', 8, 2)固定小数金額計算は decimal 一択。float は誤差が出る
enum('status', ['draft','published'])列挙型MySQL 限定機能。Laravel 14 で enum() は非推奨予定
timestamps()created_at / updated_atnullableTimestamps() も選択可
softDeletes()deleted_at ソフト削除withTrashed() で取得可能

インデックス・制約早見表

メソッド / 型例目的一言メモ
foreignId('user_id')->constrained()外部キー + インデックスconstrained()users.id を推測
index(['a', 'b'])複合インデックスカーディナリティの高い列を先頭に
unique('email')一意制約NULL を許すかは DB ごとに挙動が違う
fullText(['title','body'])全文検索インデックスMySQL 5.7+ / InnoDB 限定
spatialIndex('location')GIS インデックスpoint, polygon など地理情報向け

NG 例:nullable FK と順序ミス

$table->foreignId('user_id')->nullable();
$table->foreign('user_id')->references('id')->on('users');

nullable() を指定すると 外部キー制約違反が起きにくい が、参照整合性が崩れやすい。実運用では SET NULL / CASCADEonDelete を明示するか、NOT NULL + デフォルト値を検討しよう。

テーブル変更を安全に運用するコツ

そもそも「テーブルを変える」とは?

  • 追加: 新しいカラムやインデックスを足す
  • 変更: 既存カラムの型・長さ・NULL 制約を変える
  • 削除: 不要になったカラム・インデックスを落とす

Laravel のスキーマビルダには addColumn()dropColumn()renameColumn() など多数あるが、型や制約を直接変更するメソッドが change()。便利だが、裏では ALTER TABLE ... MODIFY が実行される。

MySQL 5.7 以前や Aurora MySQL では ALTER TABLE が全行ロックを取得することがあり、巨大テーブルでは 数十秒〜数分サービスが停止 するリスクがある。

操作例MySQL / MariaDBPostgreSQL備考
新しい nullable カラムを追加非ロック(即時適用)非ロック比較的安全
非 NULL カラムを追加 (default 無し)全行バックフィルテーブルリライトデフォルト値を付けて後で alter が安全
varchar(100) → varchar(255)全行コピーメタデータ変更のみMySQL は行コピーが走る
INT → BIGINTテーブルコピーテーブルリライト書込み停止時間が長い
カラム削除 / 変更テーブルコピーテーブルリライト最もロック時間が長い

Laravel 12 新機能2つで DX 爆上げ

php artisan schema:dump --prune ― Migration を SQL スナップショット化

schema:dump --prune「過去のマイグレーション > 1 本の SQL」 にまとめるコマンド。差分を整理できる一方、いくつか副作用もある。

何が起きる?

  • 現在の DB スキーマを database/schema/*.sql にダンプ。
  • database/migrations/*.php から実行済みファイルを削除(--prune)。
  • 以後 php artisan migrate① スナップショット → ② 残りの未実行マイグレーション の順に適用。

メリット

  1. 初期構築が速い — CI で migrate が数十秒→数秒になる。
  2. ファイル数が減る — 100 本以上の古いファイルを整理できる。
  3. Git で差分が追いやすい — 大量の自動生成ファイルをレビューせずに済む。

🚨 副作用・注意点

項目内容回避策
ロールバック不可ダンプより前の状態へ migrate:rollback できない古いブランチが必要なら dump 前に残すか、タグを切る
旧マイグレーションが HEAD から削除される順序や経緯を git log で辿る手間が増える(履歴コミットには残る)dump 前にタグを切る・Wiki/ADR に経緯を記録
dump ファイルが肥大化大規模 DB ではダンプ SQL が数 MB〜数十 MB。差分 1 行でも 巨大ファイル全体が変更扱い となり Git でコンフリクトしやすいdump は 最後に 1 人がまとめて実行 する / PR 前に schema:dump を再実行して衝突を解消 / テーブル単位に分割ダンプする
関数・トリガ・ビューDB により一部含まれない場合あり追加で after_dump.sql などに手動記述
DB ごとの差分MySQL と SQLite で SQL 方言が違うテスト用接続ごとに --database で個別ダンプ

推奨手順

# 本番と同じ接続でスナップショット作成 & 古いファイル削除
php artisan schema:dump --prune

# テスト用 DB にも作成
php artisan schema:dump --database=testing --prune
  1. ダンプ後は 必ずコミット。CI・本番で同じファイルを使う。
  2. 新しいマイグレーションを追加したら、定期的に再度 schema:dump --prune でクリーンアップ。

shouldRun() で機能フラグ付き Migration. shouldRun() で機能フラグ付き Migration

use Laravel\Pennant\Feature;
use App\Features\Bookings;

public function shouldRun(): bool
{
    // 機能フラグが ON なら走らせる
    return Feature::active(Bookings::class);
}

テスト連携の基本

  • use RefreshDatabase; でテストごとにトランザクション → ロールバック
  • schema:dump をコミットしておけば migrate:freshが一瞬で完了。CI のタイムアウトに怯えない。
  • Faker でダミーデータ → Factory / Seeder を使い回し、E2E でも同じシナリオを共有。

Faker活用ライトガイド – “それっぽい” ダミーデータを 5 秒で錬成する

よくあるエラーと速攻対処集

Laravel + MySQL で初心者がつまずきやすい代表的なエラーを「症状 → 原因 → 対処」の順でまとめました。順位やランキングではなく、遭遇頻度が高いものから順に並べています。

1071 Key too long — インデックス長オーバー

症状: index()unique() を貼った文字列カラムで発生。

原因: utf8mb4 4 バイト文字 × 長い文字数で、インデックス長が MySQL の 767 バイト制限を超過。

対処コード例

// ① フレームワーク全体でデフォルト長を狭める
Schema::defaultStringLength(191);

// ② またはカラム長を縮める
$table->string('email', 191)->unique();

Unknown collation utf8mb4_0900_ai_ci — Collation 非対応

症状: MySQL 5.6 / 5.7 などで Laravel 12 を動かすと発生。

原因: utf8mb4_0900_ai_ci は MySQL 8.0 用。古いバージョンは理解できない。

対処例

# .env
DB_COLLATION=utf8mb4_general_ci

またはマイグレーションで明示的に collation() を指定。

Cannot drop column 'x' used by a view — View が依存

症状: カラム変更や削除時にエラー。

原因: そのカラムを参照する View が存在し、MySQL が整合性を守るため拒否。

対処手順

  1. 依存している View を DROP VIEW または再定義。
  2. マイグレーションでカラムを削除。
  3. 必要なら View を再作成。

Foreign key constraint fails — 外部キー制約違反

症状: レコード挿入・削除・更新時に制約エラー。

原因: 親レコードが無い(孤児)/子テーブルが参照中など整合性が崩壊。

対処例

// 外部キーに ON DELETE CASCADE を追加
$table->foreignId('post_id')
      ->constrained()
      ->onDelete('cascade');

// 既存の孤児を掃除
DELETE FROM comments WHERE post_id NOT IN (SELECT id FROM posts);

Syntax error in SQL generated by Doctrine — 複雑な change()

症状: change() で型や制約を複数同時に変更すると発生しやすい。

原因: Doctrine DBAL が生成する ALTER TABLE ... CHANGE が対象 DB の文法に合わない。

対処の鉄則

// NG: 一度に複数の変更を試みる
$table->string('name', 200)->nullable()->change();

// OK: 分割して安全な順序で実行
$table->dropColumn('name');
$table->string('name', 200)->nullable();

まとめ

ここまでで Laravel マイグレーションの基本操作から安全なスキーマ変更、テスト連携までひと通り眺めました。この記事が最初の一歩を踏み出す助けになれば幸いです!


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください