🈳

[Laravel 11] nullOnUpdate()で親レコード更新時に外部キーをnullにできるようになった

2024/12/07に公開2

概要

Laravel 11.24.0から、マイグレーション定義でnullOnUpdate()メソッドが使えるようになったようです!
これを使えば、親レコードのIDが変更されたときに、外部キーを自動的にnullにする設定を簡単に記述できます。
https://github.com/laravel/framework/pull/52798

マイグレーションの例

以下は、actorsテーブルでnullOnUpdate()を使った外部キーの定義例です。

<?php

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

return new class extends Migration {
    public function up()
    {
        Schema::create('actors', function (Blueprint $table) {
            $table->unsignedBigInteger('id')->primary();

            $table->foreignId('user_id')
                ->nullable()
                ->constrained()
                ->nullOnDelete()
                ->nullOnUpdate(); // 親ID変更時にnullにする設定

            $table->string('name');
            $table->timestamps();
        });
    }

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

これで、親IDが変更された場合、外部キーが自動的にnullにリセットされるようになります。

挙動テスト

php artisan tinkerで実際に動作を確認してみました。

// Userレコード作成
$user = App\Models\User::factory()->create(['id' => 1001]);

// Actorレコード作成(外部キーとしてuser_id=1001を設定)
$actor = App\Models\Actor::factory()->create(['user_id' => 1001]);

// 親レコードのIDを変更
$user->update(['id' => 1002]);

// Actorレコードを再取得
$actor->refresh();

App\Models\Actor {
    id: 5152,
    user_id: null, // nullに更新されている
    name: "Ms. Leslie Shields",
    created_at: "2024-12-07 08:30:10",
    updated_at: "2024-12-07 08:30:10",
}

IDを変更した際に、参照している外部キーが自動的にnullになることがわかります

ユースケース

IDは基本的に不変な識別子として使われるべきですが、そうではないプロジェクトに遭遇したことがあります。
外部システムなどで発番された社員番号をインポートするなどで、IDの値が意味を持ってしまっているケースです。
例: ID=1001〜10000を管理ユーザー、ID=9000〜9999を一時ユーザーとして扱う

このような場合、次のようなID変更が想定されます。

  • 一時ユーザー(IDが9000番台)の本登録(例: IDを9001 → 1001に変更)
  • 管理ユーザーへの昇格(例: IDを20001 → 1001に変更)

こういった場合、関連する子レコードの外部キーをnullにリセットしたい場合があるかなと、、
nullOnUpdate()を活用することで、このような処理を簡単に実現できますね。

まとめ

nullOnUpdate()は、親レコードのID変更に対応した外部キーの管理を簡潔に実現できるので、IDに意味を持ってしまっているプロジェクトで使えそうですね。[1]

脚注
  1. そういうプロジェクトはバージョンが古い傾向にあるので、->nullOnUpdate()ではなく->onUpdate('set null')を使う必要がありそうですね ↩︎

Discussion

nshironshiro

Laravel の外部キー周りは、バリエーションが色々あって少しややこしいですね😂
(ある意味、進化しているという事ですが…)

追加された->nullOnUpdate() は、->onUpdate('set null') の簡略記法という感じですかね。
丁度、->nullOnDelete() が、->onDelete('set null') の簡略版という感じで。
実際、->onUpdate('set null') でも問題無く動作しました😊

MaruMaru

nshiroさんコメントいただきありがとうございます☺️
なるほど簡略記法が追加されただけで以前から実現は可能だったのですねー!
記事にも注釈つけておきました!