Laravel で初めてデータベースを触る人でも、「マイグレーションって何を、どうやって、なぜやるのか」 が最短で腹落ちすることを目指したガイドです。この記事を読み終えるころには、テーブル作成から安全なスキーマ変更、テストへの組み込みまで “ひととおり回せる” スキルセットが身につきます。
- 1. はじめに ― Laravelのmigrationってなにするの?
- 1.1. 3 行で動く最小サンプル
- 2. マイグレーションの作法 ― コマンドを「使う順」で覚える
- 2.1. 作成 — 雛形を切る
- 2.2. 適用 / 戻す — 反映・検証
- 2.3. 作り直し — リセット / 一括実行
- 2.4. Migration コマンド早見表(チートシート)
- 3. スキーマ定義のツボ ― 型・インデックス早見表 + 失敗例
- 3.1. カラム属性早見表
- 3.2. インデックス・制約早見表
- 4. テーブル変更を安全に運用するコツ
- 4.1. そもそも「テーブルを変える」とは?
- 5. Laravel 12 新機能2つで DX 爆上げ
- 5.1. php artisan schema:dump –prune ― Migration を SQL スナップショット化
- 5.1.1. 何が起きる?
- 5.1.2. メリット
- 5.1.3. 🚨 副作用・注意点
- 5.1.4. 推奨手順
- 5.2. shouldRun() で機能フラグ付き Migration. shouldRun() で機能フラグ付き Migration
- 6. テスト連携の基本
- 7. よくあるエラーと速攻対処集
- 7.1. 1071 Key too long — インデックス長オーバー
- 7.2. Unknown collation utf8mb4_0900_ai_ci — Collation 非対応
- 7.3. Cannot drop column ‘x’ used by a view — View が依存
- 7.4. Foreign key constraint fails — 外部キー制約違反
- 7.5. Syntax error in SQL generated by Doctrine — 複雑な change()
- 8. まとめ
はじめに ― Laravelのmigrationってなにするの?
Laravel のマイグレーションは 「DB スキーマを PHP クラスでバージョン管理する仕組み」。git push
と同じ感覚で php artisan migrate
を叩けば、コードとデータベースの状態が常に揃う。SQL を手書きするより 事故率が下がる理由 は3つだけ覚えれば OK。
- 差分管理 ― 変更履歴が Git に残る
- 自動ロールバック ―
down()
を書けばワンコマンドで巻き戻し - テストとの親和性 ― テスト前に
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_at | nullableTimestamps() も選択可 |
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
/ CASCADE
の onDelete
を明示するか、NOT NULL + デフォルト値を検討しよう。
テーブル変更を安全に運用するコツ
そもそも「テーブルを変える」とは?
- 追加: 新しいカラムやインデックスを足す
- 変更: 既存カラムの型・長さ・NULL 制約を変える
- 削除: 不要になったカラム・インデックスを落とす
Laravel のスキーマビルダには addColumn()
・dropColumn()
・renameColumn()
など多数あるが、型や制約を直接変更するメソッドが change()
。便利だが、裏では ALTER TABLE ... MODIFY
が実行される。
MySQL 5.7 以前や Aurora MySQL では
ALTER TABLE
が全行ロックを取得することがあり、巨大テーブルでは 数十秒〜数分サービスが停止 するリスクがある。
操作例 | MySQL / MariaDB | PostgreSQL | 備考 |
---|---|---|---|
新しい 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
は ① スナップショット → ② 残りの未実行マイグレーション の順に適用。
メリット
- 初期構築が速い — CI で
migrate
が数十秒→数秒になる。 - ファイル数が減る — 100 本以上の古いファイルを整理できる。
- 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
- ダンプ後は 必ずコミット。CI・本番で同じファイルを使う。
- 新しいマイグレーションを追加したら、定期的に再度
schema:dump --prune
でクリーンアップ。
shouldRun()
で機能フラグ付き Migration. shouldRun()
で機能フラグ付き Migration
- Migration クラスにメソッドを追加するだけ。Pennant との相性◎。
↳ 参考: Laravel News – 12.4.0 Release
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 が整合性を守るため拒否。
対処手順
- 依存している View を
DROP VIEW
または再定義。 - マイグレーションでカラムを削除。
- 必要なら 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 マイグレーションの基本操作から安全なスキーマ変更、テスト連携までひと通り眺めました。この記事が最初の一歩を踏み出す助けになれば幸いです!
コメントを残す