◀ 4.

Laravelにおけるモデルの(複合)主キーの指定方法とインクリメントについて

▶
この記事の要点
  • Eloquent の既定は「主キー名 id・整数・自動採番」。違う場合だけモデルで上書きする
  • 主キーのカラム名を変える: protected $primaryKey = 'user_id';
  • 自動採番しない(自分で値を入れる): public $incrementing = false;
  • 主キーが文字列: protected $keyType = 'string';。付け忘れると値が整数に変換されて 0 になることがある
  • UUID / ULID 主キーは HasUuids / HasUlids トレイトを使うと 3 つの設定をまとめて済ませられる
  • Eloquent は複合主キーに対応していない。$primaryKey に配列を指定しても正しく動かない

Eloquent の主キーの既定値

Laravel の Eloquent モデルは、何も設定しなければ主キーを次のように扱います。テーブルがこの形なら、モデルに主キーの設定を書く必要はありません。

プロパティ既定値意味
$primaryKey'id'主キーのカラム名
$incrementingtrueDB の自動採番(AUTO_INCREMENT など)で値が決まるか
$keyType'int'主キーの型('int' か 'string')

マイグレーションの $table->id(); は「id という名前の自動採番の BIGINT 主キー」を作るので、この既定値とそのまま一致します。

主キーのカラム名を変える($primaryKey)

Laravel のモデルに主キーを指定するには、以下の様に $primaryKey にカラム名を代入します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $primaryKey = 'user_id';
}

User::find(10) は WHERE user_id = 10 になり、リレーションの既定の外部キーや $user->getKey() もこの設定に従います。古い Laravel(7 以前)ではモデルの名前空間が App でしたが、Laravel 8 以降は App\Models が標準です。

自動採番しない主キー($incrementing)

主キーがインクリメントしない(社員番号などをアプリ側で決める)場合は $incrementing を false とします。

class Employee extends Model
{
    protected $primaryKey = 'employee_code';
    public $incrementing = false;
    protected $keyType = 'string';
}

// 保存時に自分で主キーを入れる
Employee::create(['employee_code' => 'E0001', 'name' => '山田']);

$incrementing が true のままだと、INSERT 後に Laravel が DB の「最後に採番された ID」を主キーに上書きしようとします。自動採番でないテーブルでは 0 などの値が入り、直後の $model->employee_code や save() による更新が意図しない動きになります。

文字列の主キー($keyType)

主キーが文字列なのに $keyType を 'string' にしないと、$incrementing = true の状態では主キーが整数にキャストされ、'ABC' が 0 になるといった現象が起きます。文字列の主キーでは $incrementing = false と $keyType = 'string' をセットで書くのが安全です。

UUID / ULID を主キーにする

UUID や ULID を主キーにする場合は、専用のトレイトを使うと $incrementing / $keyType の設定と、作成時の値の自動生成をまとめて行ってくれます。

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    use HasUuids;   // ULID なら HasUlids
}
// マイグレーション
Schema::create('orders', function (Blueprint $table) {
    $table->uuid('id')->primary();   // ULID なら $table->ulid('id')->primary();
    $table->timestamps();
});

複合主キーは Eloquent では使えない

旧版の本記事では次のように $primaryKey に配列を指定する方法を紹介していましたが、Eloquent は複合主キーをサポートしていません(公式ドキュメントにも明記されています)。

// これは正しく動かない
protected $primaryKey = ['user_id', 'user_type'];

この指定でも一部の取得処理は動いたように見えることがありますが、find()、save() による更新、delete()、リレーションなど、主キーを 1 つの値として扱う処理でエラーや誤動作になります。複合キーのテーブルを扱う方法は次のとおりです。

  1. サロゲートキーを追加する(推奨): $table->id(); で自動採番の主キーを持たせ、元の複合キーには複合ユニーク制約を付ける
  2. モデルの主キーは片方だけにし、更新・削除はクエリビルダで条件を書く: DB::table('user_roles')->where('user_id', 1)->where('user_type', 'admin')->update([...])
  3. 多対多の中間テーブルであれば、モデルを作らず belongsToMany の attach() / detach() / sync() で操作する
// 1. のマイグレーション例
Schema::create('user_roles', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->string('user_type');
    $table->unique(['user_id', 'user_type']);   // 元の複合キーは一意制約で守る
});

DB 側で複合主キーを作るだけなら、マイグレーションで $table->primary(['user_id', 'user_type']); と書けます。制約が付かないのは Eloquent モデルの側だという点に注意してください。

確認方法

  • php artisan model:show User で、モデルが認識している主キー・テーブル・カラムの情報を一覧できる
  • php artisan tinker で (new App\Models\User)->getKeyName() を実行し、主キー名を確認する
  • 保存直後に dump($model->getKey()) で、期待した値(0 になっていないか)を確認する

関連

Post Share
子ページ

子ページはありません

同階層のページ
  1. モデルの作成
  2. $fillable $guarded $hiddenの説明
  3. テーブルの紐づけ
  4. 主キーの指定とインクリメント
  5. タイムスタンプ
  6. モデルでselect
  7. モデルでinsert
  8. モデルでupdate
  9. 現在値に加算する方法
  10. created_at/updated_atの別名指定