【第四弾】Laravel入門資料

📚 Laravel公式ドキュメント - Database: Migrations

https://laravel.com/docs/12.x/migrations

💡 この講座では、上記の公式ドキュメントを基に解説していきます。
公式ドキュメントは初学者には内容が難しいため、エッセンスを優しく噛み砕いて解説していきます。

マイグレーション(Migration)とは

マイグレーションとは、データベースのテーブル構造をコードで管理する仕組みです。

マイグレーションなしの問題点

従来のデータベース管理では、以下のような問題がありました:

  • 手動でSQLを実行してテーブルを作成
  • チームメンバー間でテーブル構造が異なる
  • 本番環境と開発環境で構造が違う
  • 変更履歴が追えない
  • ロールバック(元に戻す)ができない

マイグレーションのメリット

  • バージョン管理 - Gitでテーブル構造を管理できる
  • 再現性 - コマンド1つで同じ構造を作れる
  • チーム開発 - 全員が同じ構造で開発できる
  • ロールバック - 変更を簡単に元に戻せる
  • 履歴管理 - いつ何が変更されたかわかる

💡 例え話

マイグレーションは「データベースのGit」のようなものです。
コードと同じように、データベースの変更履歴を管理し、いつでも特定の状態に戻せます。

データベース設定

マイグレーションを実行する前に、データベースの接続設定を確認します。

💡 前提条件

  • ✅ MySQLがインストール済み
  • .env ファイルでデータベース接続設定済み
  • ✅ データベースGUIツール導入済み
    • • Mac: Sequel Ace
    • • Windows: A5M2

.envファイルの設定例

プロジェクトのルートディレクトリにある .env ファイル:

# .env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=your_password

接続確認

データベースに接続できるか確認します。

php artisan migrate:status

エラーが出なければ、データベース接続成功です!

⚠️ 注意

.env ファイルを変更した場合、キャッシュをクリアする必要があります:

php artisan config:clear

マイグレーションの作成

実際にマイグレーションファイルを作成してみましょう。

基本的な作成コマンド

php artisan make:migration create_products_table

これにより、database/migrations/ ディレクトリに新しいマイグレーションファイルが作成されます。

ファイル名の規則

作成されるファイル名の例:

2025_01_22_123456_create_products_table.php

タイムスタンプ + アンダースコア + 指定した名前

💡 タイムゾーン設定

マイグレーションファイルのタイムスタンプは、デフォルトでUTC(協定世界時)で作成されます。
日本時間で作成したい場合は、以下の設定を行ってください。

1. config/app.php を編集

// 変更前
'timezone' => 'UTC',

// 変更後
'timezone' => env('APP_TIMEZONE', 'UTC'),

2. .env ファイルに追加

APP_TIMEZONE=Asia/Tokyo

3. キャッシュをクリア

php artisan config:clear

これで、マイグレーションファイルのタイムスタンプやデータベースの日時データが日本時間(JST)になります。

命名規則

目的 命名例
テーブル作成 create_products_table
カラム追加 add_price_to_products_table
カラム変更 modify_description_in_products_table
テーブル削除 drop_old_products_table

💡 命名規則のポイント

  • 1. スネークケースを使う
    小文字 + アンダースコア(例: create_products_table)
  • 2. テーブル名は複数形にする
    • ❌ 単数形: create_product_table
    • ✅ 複数形: create_products_table
  • 3. なぜ複数形?
    テーブルは「データの集合」を表すため、複数形が自然です。
    また、Laravelの規約として、モデル名(単数形)とテーブル名(複数形)が対応します。
    例: モデル Product → テーブル products

💡 重要な注意点

マイグレーションファイルの命名によって、生成されるテンプレートが少し変わります。

  • create_xxx_table パターン
    • Schema::create() を使ったテンプレートが生成される
    • • 新しいテーブルを作成する時に使う
  • add_xxx_to_yyy_table パターン
    • Schema::table() を使ったテンプレートが生成される
    • • 既存のテーブルにカラムを追加する時に使う

ただし、最終的な実装は自分で書く必要があります
命名規則に従うことで、チームメンバーが「このマイグレーションは何をするか」を名前から推測できるようになります。

テーブル構造の定義

作成されたマイグレーションファイルを開いて、テーブル構造を定義します。

マイグレーションファイルの構造

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

up() メソッド

up() メソッドは、マイグレーション実行時に呼ばれます。
ここにテーブル作成やカラム追加の処理を書きます。

down() メソッド

down() メソッドは、ロールバック時に呼ばれます。
up() で行った処理を元に戻す処理を書きます。

💡 重要な原則

up()down()対になる必要があります。
• up() でテーブル作成 → down() でテーブル削除
• up() でカラム追加 → down() でカラム削除

カラムの定義

商品テーブルを例に、実際のカラムを定義してみましょう。

public function up(): void
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();                           // id (BIGINT, AUTO_INCREMENT, PRIMARY KEY)
        $table->string('name');                 // 商品名 (VARCHAR 255)
        $table->text('description');            // 説明 (TEXT)
        $table->integer('price');               // 価格 (INT)
        $table->integer('stock')->default(0);  // 在庫数 (INT, デフォルト0)
        $table->boolean('is_published')->default(false); // 公開状態 (BOOLEAN)
        $table->timestamps();                   // created_at, updated_at
    });
}

よく使うカラム型

実務でよく使うカラム型のみを紹介します。他にも多くの型がありますが、まずはこれらを覚えましょう。
データ型は、MySQLで実際に作成されるカラムの型です。

💡 主キー(Primary Key)とは

主キーは、テーブルの各レコード(行)を一意に識別するためのカラムです。
• 重複しない(ユニーク)
• NULL(空)にならない
• 自動的に連番(1, 2, 3...)が割り振られる

Laravelの id() メソッドは、MySQLの BIGINT型(大きな整数)で主キーを作成します。

Laravelメソッド MySQLデータ型 用途
id() BIGINT UNSIGNED 主キー(1, 2, 3...と自動採番)
string('name') VARCHAR(255) 短いテキスト(名前、タイトル、メールアドレス)
text('description') TEXT 長いテキスト(説明、本文、コメント)
integer('price') INT 整数(価格、数量、年齢)
boolean('is_active') TINYINT(1) 真偽値(0=false, 1=true)有効/無効、公開/非公開
foreignId('user_id') BIGINT 外部キー(他のテーブルとの関連)
timestamps() TIMESTAMP created_at, updated_at(作成日時、更新日時)

💡 補足

この講座では基本的なカラム型のみ紹介しています。
他にも decimal()(小数点付き数値)、date()(日付)、json()(JSON)など、様々な型があります。
詳しくは公式ドキュメントを参照してください。

📝 MySQLでのboolean型について
MySQLには真のBOOLEAN型は存在しません。boolean() メソッドは、実際には TINYINT(1) 型として作成されます。
0 = false(偽)
1 = true(真)
Laravelが自動的に true/false として扱ってくれるので、通常は意識する必要はありません。

よく使うカラム修飾子

カラムに追加の設定を加えることができます。実務でよく使う修飾子のみを紹介します。

修飾子 説明
nullable() NULL許可(空でもOK) $table->string('phone')->nullable()
default($value) デフォルト値を設定 $table->integer('stock')->default(0)
unique() 一意制約(重複不可) $table->string('email')->unique()
comment('説明') カラムの説明を追加 $table->string('name')->comment('商品名')

💡 comment() について

comment() を使うと、データベースのカラムに日本語の説明を付けられます。
GUIツール(Sequel AceやA5M2)でテーブルを見た時に、何のカラムか一目でわかるので便利です。

実践例: 商品テーブル

public function up(): void
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name')->comment('商品名');
        $table->text('description')->nullable()->comment('商品説明');
        $table->integer('price')->comment('価格');
        $table->integer('stock')->default(0)->comment('在庫数');
        $table->boolean('is_published')->default(false)->comment('公開状態');
        $table->timestamps();
    });
}

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

✅ これで商品テーブルの構造が定義できました!

マイグレーションの実行

定義したマイグレーションを実行して、実際にテーブルを作成します。

基本的な実行コマンド

php artisan migrate

このコマンドで、まだ実行されていないマイグレーションが全て実行されます。

実行結果の例

$ php artisan migrate

   INFO  Running migrations.

  2025_01_22_000001_create_products_table ............... 15ms DONE

マイグレーションの状態確認

php artisan migrate:status

どのマイグレーションが実行済みかを確認できます。

📊 実行結果の例:

Migration name .......................... Batch / Status
2014_10_12_000000_create_users_table ........ [1] Ran
2025_01_22_000001_create_products_table ..... [1] Ran

マイグレーションの仕組み

Laravelは、migrations というテーブルで実行履歴を管理しています。

🔍 実行の流れ

  1. 1. migrations テーブルを確認
    • • どのマイグレーションが実行済みかチェック
  2. 2. 未実行のマイグレーションを実行
    • • ファイル名のタイムスタンプ順に実行
    • • up() メソッドを呼び出す
  3. 3. migrations テーブルに記録
    • • 実行したマイグレーションを記録
    • • 次回は実行されない

よく使うオプション

コマンド 説明
migrate 未実行のマイグレーションを実行
migrate:status マイグレーションの状態を確認
migrate --force 本番環境で強制実行(本番では慎重に!)
migrate --pretend 実行せずにSQLを表示(確認用)

💡 本番環境での安全機構

Laravelは .env ファイルの APP_ENV=production で本番環境を判断します。
本番環境で migratemigrate:fresh などを実行すると、以下のような確認メッセージが表示されます:

Do you really wish to run this command? (yes/no)

yes と入力すれば実行できますが、この確認は誤操作を防ぐための安全機構です。
--force オプションを付けると、この確認をスキップして直接実行できます(CI/CDでの自動デプロイ時に便利)。

✅ これでテーブルが作成されました!

ロールバックとリフレッシュ

マイグレーションは、実行した処理を元に戻すことができます。

ロールバック

最後に実行したマイグレーションを元に戻します。

php artisan migrate:rollback

これにより、down() メソッドが実行され、テーブルが削除されます。

特定のステップ数だけロールバック

# 最後の3つのバッチをロールバック
php artisan migrate:rollback --step=3

リフレッシュ(全削除して再実行)

php artisan migrate:refresh

全てのマイグレーションをロールバック(down()実行)してから、再度実行(up()実行)します。
開発中にテーブル構造を変更した時に使います。

フレッシュ(全削除して再作成)

php artisan migrate:fresh

全てのテーブルを強制的に削除(DROP)してから、マイグレーションを実行します。
down() メソッドを使わないので、refresh より高速で確実です。

💡 refresh と fresh の違い

コマンド 削除方法 速度
refresh down() メソッドでロールバック 遅い
fresh 全テーブルをDROP(強制削除) 高速

おすすめ: 開発中は migrate:fresh --seed を使うのが一般的です。
高速で確実にテーブルを再作成し、テストデータも投入できます。

⚠️ 警告

refreshfresh全データが削除されます!
本番環境では絶対に使わないでください。開発環境のみで使用してください。

コマンド比較

コマンド 動作 用途
migrate 未実行のマイグレーションを実行 通常の実行
migrate:rollback 最後のバッチをdown()で元に戻す 直前の変更を取り消し
migrate:refresh 全てをdown()で削除 → 再実行 開発中の再構築(遅い)
migrate:fresh 全テーブルをDROP → 再実行 開発中の再構築(高速・推奨)

実践例: 開発中の流れ

# 1. マイグレーション作成
php artisan make:migration create_products_table

# 2. ファイルを編集してテーブル構造を定義

# 3. マイグレーション実行
php artisan migrate

# 4. テーブル構造を変更したい!

# 5. 一旦削除して再実行
php artisan migrate:fresh

# 6. 再度実行
php artisan migrate

✅ ロールバックを使えば、安心してテーブル構造を変更できます!

カラムの追加・変更

既存のテーブルに対して、カラムを追加・変更する方法を学びます。

カラム追加のマイグレーション作成

既存の products テーブルに新しいカラムを追加する例です。

php artisan make:migration add_category_to_products_table

カラム追加の実装

<?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::table('products', function (Blueprint $table) {
            $table->string('category')->nullable()->after('name');
        });
    }

    public function down(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->dropColumn('category');
        });
    }
};

💡 重要なポイント

  • Schema::create() ではなく Schema::table() を使う
  • after('name') で挿入位置を指定できる
  • down() では dropColumn() でカラムを削除

カラム変更のマイグレーション

既存のカラムの型や属性を変更することができます。

💡 doctrine/dbal について(Laravel 10以前)

Laravel 10以前では、カラムを変更する際に doctrine/dbal パッケージのインストールが必要でした。
しかし、Laravel 11以降(現在はLaravel 12)では不要になりました。
Laravelがネイティブでカラム変更をサポートしています。

# Laravel 10以前では必要だった
composer require doctrine/dbal

# Laravel 11以降は不要!

カラム変更の実装

php artisan make:migration modify_description_in_products_table
public function up(): void
{
    Schema::table('products', function (Blueprint $table) {
        // descriptionをnullableに変更
        $table->text('description')->nullable()->change();

        // nameの長さを100文字に変更
        $table->string('name', 100)->change();
    });
}

public function down(): void
{
    Schema::table('products', function (Blueprint $table) {
        $table->text('description')->change();
        $table->string('name', 255)->change();
    });
}

カラムのリネーム

public function up(): void
{
    Schema::table('products', function (Blueprint $table) {
        $table->renameColumn('old_name', 'new_name');
    });
}

public function down(): void
{
    Schema::table('products', function (Blueprint $table) {
        $table->renameColumn('new_name', 'old_name');
    });
}

複数カラムの削除

public function up(): void
{
    Schema::table('products', function (Blueprint $table) {
        $table->dropColumn(['category', 'old_price']);
    });
}

外部キー制約

テーブル間のリレーション(関連)を定義するために、外部キー制約を設定します。
これは非常に重要な概念なので、しっかり理解しましょう。

外部キーとは

外部キーは、あるテーブルのカラムが、別のテーブルの主キーを参照する仕組みです。
データの整合性を保つために使われます。

💡 具体例で理解する

商品(products)とカテゴリー(categories)の関係を考えてみましょう。

categories テーブル
id name
1家電
2食品
products テーブル
id name category_id →参照先
1ノートPC1✓ 家電
2コーヒー2✓ 食品
3マウス999✗ 存在しない

外部キー制約がないと: category_id=999 のように、存在しないカテゴリーを指定できてしまう
外部キー制約があると: 存在しないカテゴリーIDは登録できない(エラーになる)

なぜ外部キー制約を使うべきなのか?

🚨 外部キーなしの問題点

  • 1. データの不整合が起きる
    存在しないカテゴリーIDを持つ商品が作れてしまう
  • 2. 孤立したデータが残る
    カテゴリーを削除しても、そのカテゴリーIDを持つ商品が残る
  • 3. バグの原因になる
    存在しないカテゴリーを参照してエラーが発生
  • 4. アプリ側で整合性チェックが必要
    データベースで保証されないので、PHP側で毎回チェックが必要

✅ 外部キーありのメリット

  • 1. データの一元管理(正規化)
    カテゴリー名を1箇所変更すれば、全商品に反映される。
    categories テーブルで「家電」→「電化製品」に変更するだけでOK
  • 2. データベースレベルで整合性を保証
    不正なデータは絶対に入らない
  • 3. 削除時の動作を制御できる
    親削除時に子も削除、または削除を防ぐなど
  • 4. バグを未然に防ぐ
    データの不整合が原因のバグがなくなる
  • 5. 開発が楽になる
    整合性チェックをアプリ側で書かなくていい

    ❌ 外部キーなしの場合 - PHP側で毎回チェックが必要

    // 商品を作成する時
    public function store(Request $request)
    {
        // PHP側で存在チェックが必要
        $categoryExists = Category::where('id', $request->category_id)->exists();
    
        if (!$categoryExists) {
            return back()->withErrors(['category_id' => '存在しないカテゴリー']);
        }
    
        // チェックが通ったら作成
        Product::create([
            'name' => $request->name,
            'category_id' => $request->category_id,
        ]);
    }

    ✅ 外部キーありの場合 - データベースが自動チェック

    // 商品を作成する時
    public function store(Request $request)
    {
        // 存在チェック不要!データベースが自動的にチェック
        Product::create([
            'name' => $request->name,
            'category_id' => $request->category_id,
        ]);
    
        // 存在しないcategory_idなら、データベースがエラーを返す
    }

⚠️ あえて外部キーにしない場合もある

実務では、意図的に外部キー制約を使わない場合もあります。

  • 1. ログデータ・履歴データ
    削除されたユーザーのログも残したい場合、user_id だけを保存して外部キーにしない。
    ユーザーが削除されてもログは残る。
  • 2. スナップショット(その時点のデータ)
    注文時の商品価格や商品名を保存する場合、後で商品情報が変わっても注文データは変わらない。
    外部キーではなく、カテゴリー名を直接 products テーブルに保存することもある。
  • 3. パフォーマンス重視
    大量のデータを扱う場合、外部キーチェックのオーバーヘッドを避けるため。
    アプリ側で整合性を保証する。

判断基準: データの整合性 vs 柔軟性・パフォーマンスのトレードオフを考えて決める

💡 外部キーとリレーションの関係

マイグレーション(今ここで学習中)では、データベースに外部キー制約を設定します。
これはデータの整合性を保つためのデータベースレベルの設定です。

モデル(後で学習)では、リレーションを定義します。
これはLaravelで「カテゴリーに紐づく商品を取得」のような操作を簡単に書けるようにするアプリケーションレベルの設定です。

// モデルで定義するリレーション(後で学習)
class Category extends Model
{
    public function products() {
        return $this->hasMany(Product::class);
    }
}

// こう書けるようになる
$category = Category::find(1);
$products = $category->products; // このカテゴリーの全商品を取得

まとめ:
外部キー(マイグレーション)= データベースでデータの整合性を守る
リレーション(モデル)= Laravelで関連データを簡単に取得できるようにする

詳しくはモデルの章で学習します。

【実践】カテゴリーと商品のリレーションを作る

実際に外部キー制約を設定してみましょう。今回は、最初からcreateマイグレーションに含める方法で実装します。

💡 2つのアプローチ

外部キーを追加する方法は2つあります:

1. 最初からcreateに含める(推奨)
テーブル作成時に外部キーも一緒に定義。シンプルで分かりやすい

2. 後からaddマイグレーションで追加
既存テーブルに外部キーを追加する場合に使う

ステップ1: 既存のproductsテーブルを削除

# 既存のテーブルを削除
php artisan migrate:rollback

# または全削除
php artisan migrate:fresh

ステップ2: カテゴリーテーブル作成

php artisan make:migration create_categories_table
public function up(): void
{
    Schema::create('categories', function (Blueprint $table) {
        $table->id();
        $table->string('name')->comment('カテゴリー名');
        $table->timestamps();
    });
}

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

ステップ3: 商品テーブルを外部キー付きで作成

既存の create_products_table マイグレーションファイルを編集します。

public function up(): void
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name')->comment('商品名');
        $table->text('description')->nullable()->comment('商品説明');
        $table->integer('price')->comment('価格');
        $table->integer('stock')->default(0)->comment('在庫数');
        $table->boolean('is_published')->default(false)->comment('公開状態');

        // 外部キー
        $table->foreignId('category_id')
              ->constrained()           // categories テーブルを参照
              ->onDelete('restrict');   // カテゴリーに商品がある場合は削除不可

        $table->timestamps();
    });
}

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

ステップ4: マイグレーション実行

# マイグレーション実行
php artisan migrate

✅ これで、categoriesテーブルが先に作成され、その後productsテーブルが外部キー付きで作成されます。

⚠️ 重要: マイグレーションの順序

外部キーを設定する際は、参照先のテーブルが先に存在している必要があります。
順序を間違えるとエラーになります。

正しい順序:

1. create_categories_table (2025_01_22_000001)
2. create_products_table (2025_01_22_000002)

❌ 間違った順序だと:

SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint
(table 'categories' doesn't exist)

💡 ポイント: マイグレーションファイル名のタイムスタンプで実行順序が決まります。
categories を先に作成したので、タイムスタンプが若い(古い)ファイル名になり、先に実行されます。

外部キーの書き方

シンプルな書き方(推奨)

$table->foreignId('category_id')
      ->constrained()           // categories テーブルを自動推測
      ->onDelete('cascade');

foreignId()unsignedBigInteger() のショートカット
constrained()category_id から categories テーブルを推測

明示的な書き方

$table->foreignId('category_id')
      ->constrained('categories')  // テーブル名を明示
      ->onDelete('cascade');

従来の書き方(Laravel 6以前)

$table->unsignedBigInteger('category_id');
$table->foreign('category_id')
      ->references('id')->on('categories')
      ->onDelete('cascade');

💡 書き方が変わった理由と歴史

Laravel 7(2020年3月)から、foreignId()constrained() が導入されました。

変更の理由:
• 従来の書き方は冗長で書くのが大変
• テーブル名を手動で指定する必要があった
• Laravelの規約に従っていれば自動推測できるはず
→ より簡潔で書きやすい構文に改善されました

Laravelの命名規約:
constrained() は、カラム名からテーブル名を自動推測します。
• カラム名 category_id → テーブル名 categories を推測
• カラム名 user_id → テーブル名 users を推測
• パターン: {単数形}_id{複数形} テーブル
※ この規約に従えば、テーブル名を明示しなくてもOK

onDelete() - 削除時の動作指定

親レコード(カテゴリー)を削除した時に、子レコード(商品)をどう扱うかを指定します。
これは非常に重要な設定で、ビジネスロジックによって使い分けます。

メソッド 動作 実務での用途
cascade 親を削除すると子も削除 カテゴリー削除→商品も削除 注文と注文明細など、親なしで意味がない子データ
set null 親を削除すると子はNULLに カテゴリー削除→商品はcategory_id=NULL ユーザーとコメントなど、親が消えても子を残したい場合
restrict 子が存在すると親を削除できない 商品があるカテゴリーは削除不可 本番で最も安全。誤削除を防ぐ(デフォルト)

💡 実務での使い分け

1. cascade を使う場合
• 注文(orders) と 注文明細(order_items)
• ブログ投稿(posts) と コメント(comments)(投稿が消えたらコメントも不要な場合)
→ 親が消えたら子も意味がないデータ

2. set null を使う場合
• ユーザー(users) と コメント(comments)(ユーザーが退会してもコメントは残す)
• 投稿者(users) と ブログ記事(posts)(退会しても記事は残す)
→ 親が消えても子は残したいデータ
⚠️ nullable() を忘れずに!

3. restrict を使う場合(デフォルト)
• カテゴリー(categories) と 商品(products)
• 部署(departments) と 社員(employees)
→ 誤削除を防ぎたい場合。子レコードが存在する限り、親は削除できない。
→ アプリ側で「このカテゴリーには商品が登録されているため削除できません」と表示して、ユーザーに確認を促せる。
※ onDelete() を指定しない場合は自動的に restrict になります

シーダー(Seeder)

シーダーとは、データベースに初期データを投入する仕組みです。

💡 Seederの主な用途

1. マスターデータの投入(最重要)
システムが動作するために必ず必要なデータを投入します。
例: カテゴリー、都道府県、権限、ステータス、設定値など
→ これがないとシステムが動かない

2. 開発環境のテストデータ投入
開発中に動作確認用のサンプルデータを投入します。
例: サンプル商品、サンプルユーザーなど
→ チーム全員が同じデータで開発できる

3. 本番環境の初期データ投入
本番リリース時に必要なマスターデータを投入します。
php artisan db:seed で本番環境にも投入可能

シーダーのメリット

  • マスターデータを確実に投入できる(本番環境でも使える)
  • migrate:fresh しても一発で復元できる(開発効率UP)
  • チーム全員が同じデータで開発できる(属人化防止)
  • Git管理できる(バージョン管理可能)

【実践】シーダーを作成してマスターデータを投入

カテゴリーと商品のマスターデータを投入するシーダーを作成します。

ステップ1: シーダーを作成

# カテゴリーのシーダー
php artisan make:seeder CategorySeeder

# 商品のシーダー
php artisan make:seeder ProductSeeder

database/seeders/ ディレクトリに2つのファイルが作成されます。

ステップ2: CategorySeeder を実装

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class CategorySeeder extends Seeder
{
    public function run(): void
    {
        DB::table('categories')->insert([
            ['name' => '家電', 'created_at' => now(), 'updated_at' => now()],
            ['name' => '食品', 'created_at' => now(), 'updated_at' => now()],
            ['name' => '衣類', 'created_at' => now(), 'updated_at' => now()],
            ['name' => '書籍', 'created_at' => now(), 'updated_at' => now()],
        ]);
    }
}

ステップ3: ProductSeeder を実装

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class ProductSeeder extends Seeder
{
    public function run(): void
    {
        DB::table('products')->insert([
            [
                'name' => 'ノートPC',
                'description' => '高性能なノートパソコン',
                'price' => 120000,
                'stock' => 10,
                'is_published' => true,
                'category_id' => 1,  // 家電
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'name' => 'ワイヤレスマウス',
                'description' => '快適な操作感',
                'price' => 3000,
                'stock' => 50,
                'is_published' => true,
                'category_id' => 1,  // 家電
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'name' => 'オーガニックコーヒー',
                'description' => '香り高いコーヒー豆',
                'price' => 1500,
                'stock' => 30,
                'is_published' => true,
                'category_id' => 2,  // 食品
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'name' => 'Laravel入門書',
                'description' => '初心者向けの解説書',
                'price' => 2800,
                'stock' => 20,
                'is_published' => true,
                'category_id' => 4,  // 書籍
                'created_at' => now(),
                'updated_at' => now(),
            ],
        ]);
    }
}

⚠️ 重要: シーダーの実行順序

ProductSeederは category_id で外部キーを参照しているため、 CategorySeederを先に実行する必要があります。
順序を間違えると、存在しない category_id を参照してエラーになります。

ステップ4: DatabaseSeeder で実行順序を管理

database/seeders/DatabaseSeeder.php で、実行するシーダーと順序を指定します。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            CategorySeeder::class,  // 先に実行
            ProductSeeder::class,   // 後に実行
        ]);
    }
}

ステップ5: シーダーを実行

# 全てのシーダーを実行(DatabaseSeeder の call() に書いたものを順に実行)
php artisan db:seed

# 特定のシーダーのみ実行(DatabaseSeeder に書かなくても実行可能)
php artisan db:seed --class=CategorySeeder

# migrate:fresh と同時に実行(便利!)
php artisan migrate:fresh --seed

💡 シーダーの実行の仕組み

1. php artisan db:seed(オプションなし)
→ DatabaseSeeder の call() に書かれたシーダーを順番に実行します。
→ この例では CategorySeeder → ProductSeeder の順で実行される

2. php artisan db:seed --class=CategorySeeder
→ 指定したシーダーだけを実行します。
→ DatabaseSeeder の call() に書かなくても実行可能
→ 開発中に特定のマスターデータだけ投入し直したい時に便利

3. php artisan migrate:fresh --seed
→ 全テーブル削除 → マイグレーション実行 → db:seed を実行
→ つまり DatabaseSeeder の call() に書いたシーダーを順に実行

💡 開発の流れ

# テーブル構造を変更
# ↓
php artisan migrate:fresh --seed
# ↓
# テーブル再作成 + マスターデータ投入 完了!

このワンコマンドで、いつでもクリーンな状態に戻せます。

💡 補足: Factory(中級者向け)

Seederは決まったデータ(カテゴリー、都道府県など)を投入するのに適していますが、 1000件、10000件といった大量のランダムなテストデータが必要な場合は、 Factory という機能があります。

Factoryの用途:
• PHPUnitでの自動テスト用データ生成
• パフォーマンステスト用の大量データ生成
• ランダムな商品データを1000件生成、など

ただし、Factoryは主にPHPUnitでの自動テストで使う中級者向けの機能のため、 この入門講座では扱いません
まずは Seeder を使ってマスターデータの投入ができるようになりましょう!

✅ シーダーを使えば、マスターデータの管理が楽になります!

まとめ

この講座で学んだマイグレーションの内容をまとめます。

マイグレーションの基本

操作 コマンド
マイグレーション作成 make:migration create_xxx_table
マイグレーション実行 migrate
状態確認 migrate:status
ロールバック migrate:rollback
全削除→再実行 migrate:fresh
シーダー作成 make:seeder XxxSeeder
シーダー実行 db:seed
再構築+データ投入 migrate:fresh --seed

よく使うカラム型

用途 メソッド
主キー id()
短いテキスト string('name')
長いテキスト text('description')
整数 integer('price')
真偽値(0 or 1) boolean('is_active')
外部キー foreignId('user_id')->constrained()
作成日時・更新日時 timestamps()

よく使うカラム修飾子

修飾子 意味
nullable() NULL許可(空でもOK)
default($value) デフォルト値を設定
unique() 一意制約(重複不可)
comment('説明') カラムの説明を追加

マイグレーションのベストプラクティス

  • up() と down() は必ず対にする
    ロールバックできるようにする
  • マイグレーションファイルは編集しない
    一度実行したら、新しいマイグレーションで変更する
  • 外部キーの順序に注意
    参照先テーブルを先に作成する
  • 開発中は migrate:fresh --seed を活用
    いつでもクリーンな状態に戻せる
  • ⚠️ 本番環境では fresh/refresh は絶対に使わない
    データが全て消えてしまう

次のステップ

マイグレーションでテーブル構造を定義できるようになりました。
次はモデル(Model)を学び、PHPからデータベースを操作する方法を学びます。

🎉 お疲れ様でした!

マイグレーションは、Laravelでアプリケーションを開発する上で最も重要な機能の1つです。
何度も練習して、しっかりマスターしましょう!