【Laravel】多対多の中間テーブルのモデルはPivot化するとアクセスしやすくなりメソッドを使いやすい

はじめに

多対多の関係の中間テーブルのモデルはPivot化すると、リレーション経由でアクセスしやすくなります。

中間テーブルのモデルにメソッドを持たせていて、それを使いたい場合に便利です。

目次

Pivot化していない場合

例として、Itemモデルに、多対多の関係にあるTagモデルが存在し、以下のような多対多のリレーションが定義されているとします。

class Item extends Model
{
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany('App\Tag');
    }
}

このリレーションを使って、Tagモデルを取得してみます。

$ php artisan tinker
>>> App\Item::find(1)->tags
=> Illuminate\Database\Eloquent\Collection {#2901
     all: [
       App\Tag {#2894
         id: 1,
         name: "タグ1",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#2892
           item_id: 1,
           tag_id: 1,
         },
       },
     ],
   }

リレーションを使って取得したTagモデルには、

  • pivotプロパティ

があり、そこには

  • Illuminate\Database\Eloquent\Relations\Pivot

が格納されていることがわかります。

さて、中間テーブルのモデルItemTagに何かメソッドを持たせていたとして、これを使うにはどうすれば良いでしょうか。

class ItemTag extends Model
{
    public function hello(): string
    {
        return 'Hello, world.';
    }
}

以下のようなことをやっても上手くはいきません。

$ php artisan tinker
>>> App\Item::find(1)->tags->first()->pivot->hello()
BadMethodCallException with message 'Call to undefined method Illuminate/Database/Query/Builder::hello()'

Pivot化する

まず、先ほどの定義済みのリレーションにusingメソッドを使うようにします。

class Item extends Model
{
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany('App\Tag')
            ->using('App\ItemTag'); // 追加
    }
}

その上で、中間テーブルのItemTagをモデルではなくPivotにします。

class ItemTag extends Pivot // ModelからPivotに変更
{
    public function hello(): string
    {
        return 'Hello, world.';
    }
}

こうすることで、リレーションを使って取得したTagモデルのpivotプロパティには

  • Illuminate\Database\Eloquent\Relations\Pivot

ではなく、

  • ItemTag

が格納されるようになります。

$ php artisan tinker
>>> App\Item::find(1)->tags->first()
=> App\Tag {#2868
     id: 1,
     name: "タグ1",
     pivot: App\ItemTag {#2862 // ここに注目!
       item_id: 1,
       tag_id: 1,
     },
   }
>>> App\Item::find(1)->tags->first()->pivot
=> App\ItemTag {#2865 // ここに注目!
     item_id: 1,
     tag_id: 1,
   }

これで、ItemTagにリレーション経由でアクセスし、そのメソッドを使えるようになりました。

>>> App\Item::find(1)->tags->first()->pivot->hello()
=> "Hello, world."

補足

  • もし、Pivot化はせず、リレーションにusingメソッドを使っただけの状態だと、以下のエラーになります。
>>> App\Item::find(1)->tags->first()->pivot
BadMethodCallException with message 'Call to undefined method Illuminate/Database/Query/Builder::fromRawAttributes()'
  • Pivotは、Modelを継承しています。
class Pivot extends Model
{
// 略

参考