メインコンテンツへスキップ
パッケージのモデルを移動・改名しても多態リレーションの保存データを読み続けたい場合は、モデルのクラス名とDBに保存する型名を分離します。Morph mapの別名は短縮表記というより、リリースをまたいで維持する保存形式の契約です。 このページはLaravel Framework v13.42.0 の公式実装を確認しています。リレーションの基本はEloquentリレーション入門、利用者のモデルに外から関連を追加する方法は動的リレーション登録を参照してください。

型名と関連名を分ける

多態リレーションは、関連先の主キーと型名を組み合わせて、複数種類のモデルを参照します。たとえばコメントの関連先を記事や動画にできる構成では、commentable_id だけでなく commentable_type も必要です。 未登録の場合、通常は Acme\Publishing\Models\Article のような完全修飾クラス名が型として使われます。別名を登録すると、新しい関連の作成などでは acme-publishing.article が使われます。既存行の値は登録だけでは書き換わりません。 Morph mapは関連メソッドやテーブルを作成する機能ではありません。モデル・列・インデックスは、通常どおり用意します。

パッケージ固有の別名だけを登録する

例の Acme\Publishing\ はComposerのPSR-4で src/ に対応するものとします。まず src/Models/Article.php を定義します。
src/Models/Comment.php では逆向きの関連を定義します。
acme_articles には id・title・タイムスタンプ、acme_comments には id・body・commentable_type・commentable_id・タイムスタンプを用意します。この例の主キーは整数です。$table->morphs('commentable') で作る列と複合インデックスを利用できますが、UUIDなどを使う構成では関連先と列型を一致させてください。 次に、通常の非遅延サービスプロバイダー src/PublishingServiceProvider.php の boot() で別名を登録します。
プロバイダーの読み込みはパッケージの自動検出で設定します。登録中にモデルを保存したり、DBの型名を更新したりする必要はありません。既存データの移行は別の作業です。
$alias は acme-publishing.article、$class は Article::class になります。Comment 自身が別の多態リレーションの関連先にならない限り、この例のために Comment の別名まで登録する必要はありません。

マージと名前衝突の規則を理解する

Relation::morphMap() はstaticの対応表を操作します。デフォルトのマージでは、新しい配列と既存の配列を $map + static::$morphMap で結合します。 パッケージから第2引数に false を渡すと、ほかのパッケージや利用アプリの登録を消してしまいます。通常の登録では既存表を維持し、article のような汎用名ではなく、配布元・パッケージを識別できる別名を選びます。同じ別名を異なる意味に再利用しないでください。 クラス名のリストだけを渡す形式では、Laravelは各モデルを生成してテーブル名をキーにします。保存形式をテーブル名の変更からも切り離したいパッケージでは、上の例のように別名とクラスの対応を明示します。
利用アプリが同じクラスへ別の別名を追加すると、登録順によって今後保存される値や関連クエリの条件が変わります。「両方の名前を登録したので互換性が維持される」とは限りません。クラスの差し替えを許可する場合も、保存済みデータを同じ別名で読めることを公開契約として確認します。
標準の getMorphClass() は具象モデルのクラスを探します。Article のサブクラスを利用しても、親クラスの別名が自動的に継承されるわけではありません。モデル差し替えに対応するパッケージでは、実際に使うクラスを対応表へ登録する設計が必要です。

厳格モードは利用アプリ全体の方針にする

公式ドキュメントの enforceMorphMap() は、別名の登録と requireMorphMap(true) をまとめて行います。パッケージだけに限定した強制ではありません。 v13.42.0 の標準実装では、厳格モードは次の両方向に影響します。
  • モデルから型名へ: getMorphClass() は未登録モデルに対して ClassMorphViolationException を投げます。組み込みの汎用 Pivot クラスには例外扱いがありますが、通常のモデルや独自Pivotに同じ免除はありません。
  • 保存された型名からクラスへ: getActualClassNameForMorph() は対応表のキーにない値に対して同じ例外を投げます。これは morphTo() の通常取得とEagerロードでも使われます。対応表の「値」にクラスがあっても、古い完全修飾クラス名が「キー」にないなら読み込めません。
厳格モードが無効なら、対応表にない型名はクラス名として扱われます。ただし、そのクラスが存在することや、期待する関連先であることまで保証するわけではありません。 パッケージ側では通常 morphMap() で自分のモデルだけを登録し、アプリ全体で強制するかは利用者に委ねます。利用者がすでに強制している場合、パッケージから requireMorphMap(false) に戻さないでください。

既存データを移行してから互換性を判断する

別名登録の導入は、新規インストールと既存アプリの更新で条件が異なります。公式ドキュメントも、導入時に既存の完全修飾クラス名を別名へ変換する必要があると注意しています。

逆向きの取得だけでは確認が足りない

厳格モードが無効なら、古い commentable_type のクラスが存在する間は、コメントから morphTo() で記事を取得できる場合があります。しかし記事側の morphMany() は getMorphClass() の値で型を絞り込みます。別名の登録後は acme-publishing.article を探すため、古いクラス名の行は記事側のコメント一覧から外れます。 MorphOneOrMany は生成時に型名を保持し、取得条件と新規作成の両方に使います。新旧の型名が混在しても、すべての値を自動的にOR検索するわけではありません。

更新手順をリリースに含める

次は、旧クラス名を別名へ変換する処理の断片です。対象アプリのデータ移行として実装し、サービスプロバイダーから自動実行しないでください。
この処理だけでデプロイが完結するわけではありません。
  1. 対象の *_type 列と保存値を調査し、更新前のバックアップ・件数確認・復元手順を用意します。別のテーブルから同じモデルを参照している場合は、そちらも対象です。
  2. 最初の導入では、旧クラス名を書くプロセスと別名を書くプロセスが混在しないよう、書き込み停止やメンテナンスを含む切り替え手順を決めます。大きなテーブルでは更新ロックやバッチ処理も検討します。
  3. 対応表とデータを切り替え、主キーの値は変えずに型名を変換します。移行コードの旧クラス名は、将来の改名で意味が変わる Article::class ではなく、過去に実際に保存された文字列として固定します。
  4. 旧値の残存件数、新規保存、両方向の関連取得、Eagerロードを確認し、長寿命ワーカーを更新してから書き込みを再開します。
  5. ロールバックする場合は、戻すコードが保存済みの別名を解決できるか確認します。Composerのバージョンを戻すだけでは、DBの型名は戻りません。
すでに固定した別名を維持してモデルの名前空間だけを変える場合は、対応表の値を新クラスへ変更することで、型名をそのまま利用できます。一方、別名そのものの変更はデータ移行を伴う変更として扱います。バージョン互換性管理とマイグレーションの公開・更新にも更新手順を揃えます。

保存値と両方向の取得をテストする

Orchestra Testbenchでは、対応表だけでなくDBに保存した値と両方向の取得を検証します。次の例はComposerでパッケージをオートロードし、PDO・SQLiteドライバーを用意した構成です。プロバイダーをテスト内で明示的に登録し、自動検出の検証とは分けています。
対応表と強制フラグは別々のstatic状態なので、アプリケーションを作り直すだけで初期化されるとは考えません。この例は両方を保存・復元します。本番でもリクエストやテナントごとに書き換えず、起動時の安定した登録に限定します。すでに生成したリレーションは型名を保持するため、途中の登録変更が既存インスタンスへ一律に反映されるわけでもありません。 パッケージのCIには、さらに次の回帰ケースを追加します。
  • 利用アプリがすでに厳格モードを有効にしている状態で、プロバイダーを登録しても無効化しないこと。
  • 同じ別名の競合、同じクラスへの複数の別名、差し替えた具象モデルで、公開した保存形式が変わらないこと。
  • 更新前のクラス名で保存したfixtureを用意し、移行後に旧値がなくなり、両方向の取得と件数が一致すること。
  • キューワーカーなどを含む実際の更新手順と、戻すコード・データの組み合わせで復元できること。

参照した一次情報

最終更新日 2026年11月11日