Laravelで実装するポリモーフィックリレーション(Morph)

    Laravelで実装するポリモーフィックリレーション(Morph)

    目次

      はじめに

      本記事では、Laravel上でポリモーフィック関連となるデータ構造をEloquentのリレーションでどう実装するか について紹介を行います。

      ポリモーフィック関連とは?

      1つの子テーブルから複数の親テーブルを参照する関連のことを指します。

      具体例

      例えば、ブログや商品にコメントを添付する機能を実装する場合、ポリモーフィック関連を利用するのが良いとされています。

      テーブル構造は以下のような形になります。

      posts
      - id : 主キー
      - tilte : ブログタイトル
      - content : 内容

      products
      - id : 主キー
      - name : 商品名
      - discription : 商品説明

      comments
      - id : 主キー
      - content : コメント内容
      - commentable_type : 関連レコードのモデル名
      - commentable_id : 関連レコードの主キー

      202409_morph_01この設計では、commentsテーブルcommentable_idcommentable_typeがポリモーフィック関連における外部キーとなり、postsテーブルproductsテーブルのレコードと関連付けられます。

      commentable_typeによって、コメントがどのテーブルに属するのかを特定できます。

      LaravelでのMigrationの記述方法

      Laravelではポリモーフィック関連のサポートが行われているため、簡単にMigrationを記述できます。

      例で示したcommentsテーブルのMigration記述例は以下になります。

      Schema::create('comments', function (Blueprint $table) {
      $table->text('comment')->comment('コメント内容');
      $table->morphs('commentable');
      });

      $table->morphs('〇〇');

      こちらの記述により、 ポリモーフィック関連における外部キー用のカラム(〇〇_type,〇〇_id)の作成が一括で行えます。

      Laravelでのポリモーフィックのリレーション

      Laravelのモデルでリレーションの記述を行うことによって、 子テーブルのモデルから、親テーブルのモデルの取得が行えるようになります。

      リレーションの記述、利用方法について紹介を行っていきます。

      1対1の場合

      親テーブルと子テーブルが1対1である場合のリレーションの記述、利用方式について紹介していきます。

      テーブルの構造は以下とします

      posts
      - id : 主キー
      - tilte : ブログタイトル
      - content : 内容

      products
      - id : 主キー
      - name : 商品名
      - discription : 商品説明

      comments
      - id : 主キー
      - content : コメント内容
      - commentable_type : 関連レコードのモデル名
      - commentable_id : 関連レコードの主キー

      202409_morph_02リレーションの記述方法

      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Post extends Model
      {
      public function comment()
      {
      return $this->morphOne(Comment::class, 'commentable');
      }
      }
      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Product extends Model
      {
      public function comment()
      {
      return $this->morphOne(Comment::class, 'commentable');
      }
      }
      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Comment extends Model
      {
      public function commentable()
      {
      return $this->morphTo(__FUNCTION__, 'commentable_type', 'commentable_id');
      }
      }

      リレーションの利用方法

      親モデルから子モデルを取得する方法は

      use App\Models\Post;

      $post = Post::find(1);

      $comment = $post->comment;

      となります。

      こちらにより、親モデル(Postモデル)から、子モデル(commentモデル)を取得することができます。


      逆に、子モデルから親モデルを取得する方法は

      use App\Models\Comment;

      $comment = Comment::find(1);

      $value = $comment->commentable;

      となります。

      こちらにより、子モデル(commentモデル)から親モデル(Postモデル, Productモデル)を取得することができます。

      取得されるモデルはcomment.commentable_typeに記述されているモデル名に応じて変化します。

      1対多の場合

      親テーブルと子テーブルが1対多である場合のリレーションの記述、利用方式について紹介していきます。

      1対多の場合でも、1対1と同じようなテーブル構造・リレーションの記述になります。

      テーブルの構造は以下とします。

      posts
      - id : 主キー
      - tilte : ブログタイトル
      - content : 内容

      products
      - id : 主キー
      - name : 商品名
      - discription : 商品説明

      comments
      - id : 主キー
      - content : コメント内容
      - commentable_type : 関連レコードのモデル名
      - commentable_id : 関連レコードの主キー

      202409_morph_03リレーションの記述方法

      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Post extends Model
      {
      public function comments()
      {
      return $this->morphMany(Comment::class, 'commentable');
      }
      }
      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Product extends Model
      {
      public function comments()
      {
      return $this->morphMany(Comment::class, 'commentable');
      }
      }
      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Comment extends Model
      {
      public function commentable()
      {
      return $this->morphTo(__FUNCTION__, 'commentable_type', 'commentable_id');
      }
      }

      リレーションの利用方法

      親モデルから子モデルを取得する方法は

      use App\Models\Post;

      $post = Post::find(1);

      $comments = $post->comments;

      となります。

      こちらにより、親モデル(Postモデル)から、紐づくすべての子モデル(commentモデル)を取得することができます。


      逆に、子モデルから親モデルを取得する方法は

      use App\Models\Comment;

      $comment = Comment::find(1);

      $value = $comment->commentable;

      となります。

      こちらにより、子モデル(commentモデル)から親モデル(Postモデル, Productモデル)を取得することができます。

      取得されるモデルはcomment.commentable_typeに記述されているモデル名に応じて変化します。

      多対多の場合

      親テーブルと子テーブルが多対多である場合のリレーションの記述、利用方式について紹介していきます。

      テーブルの構造は以下とします

      posts
      - id : 主キー
      - tilte : ブログタイトル
      - content : 内容

      products
      - id : 主キー
      - name : 商品名
      - discription : 商品説明

      comments
      - id : 主キー
      - content : コメント内容

      commentables
      - comment_id : コメントID
      - commentable_type : 関連レコードのモデル名
      - commentable_id : 関連レコードの主キー

      202409_morph_04リレーションの記述方法

      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Post extends Model
      {
      public function comments()
      {
      return $this->morphToMany(Comment::class, 'commentable');
      }
      }
      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Product extends Model
      {
      public function comments()
      {
      return $this->morphToMany(Comment::class, 'commentable');
      }
      }
      <?php

      namespace App\Models;

      use Illuminate\Database\Eloquent\Model;

      class Comment extends Model
      {
      public function Posts()
      {
      return $this->morphedByMany(Post::class, 'commentable');
      }

      public function Products()
      {
      return $this->morphedByMany(Product::class, 'commentable');
      }
      }

      リレーションの利用方法

      親モデルから子モデルを取得する方法は

      use App\Models\Post;

      $post = Post::find(1);

      $comments = $post->comments;

      となります。

      こちらにより、親モデル(Postモデル)から、紐づくすべての子モデル(commentモデル)を取得することができます。


      逆に、子モデルから親モデルを取得する方法は

      use App\Models\Comment;

      $comment = Comment::find(1);

      $posts = $comment->posts;
      $products = $comment->products;

      となります。

      こちらにより、子モデル(commentモデル)からそれぞれの親モデル(Postモデル, Productモデル)を取得することができます。

      まとめ

      今回紹介したリレーションによって、 Laravelではポリモーフィック関連のテーブルの場合でも、簡単に関連テーブルの取得が行えるようになります。

      実務で行っている案件上で、ポリモーフィック関連のテーブルを扱う際に「Laravel上でどのように行えばよいのか」が理解しづらかったので、今回紹介させていただきました。

      Laravelを利用した開発の手助けの一因になれれば幸いです。

      ここまで読んでいただきありがとうございました。

      参考

      Laravel 11.x Eloquent:リレーション Laravel 11.x マイグレーション

      アジアクエスト株式会社では一緒に働いていただける方を募集しています。
      興味のある方は以下のURLを御覧ください。