😺

Laravelでリレーションを元に中間テーブルから直接データを取得するスマートなやり方

2022/10/13に公開2

LaravelにてbelongsToManyのように中間テーブルがあるリレーションにて、極力データを取得せずに中間テーブルからデータを取得したいことがありました。
ちょっと調べてみたのですが、withPivotでデータを含める方法ばかりが検索にヒットするので調査をしました。

確認環境

  • Laravel 7

結論

newPivotQuery()を使うことで対応出来ます。

$post->favoriteUsers()->newPivotQuery()->pluck('user_id');
// select "user_id" from "favorite_post_users" where "post_id" = ?

思った手段

何も考えずに書く

今回、ユーザーIDの一覧が取得できればよかったので、単純に考えると以下になります。

$post->favoriteUsers()->pluck('id');
// select "id" from "users" inner join "favorite_post_users" on "users"."id" = "favorite_post_users"."user_id" where "favorite_post_users"."post_id" = ?

ただこの場合はusersテーブルから中間テーブルをinner joinして取得するクエリになります。
usersテーブルにしか存在しない情報を取得したい場合はこれを使うと良いでしょう。

DBファサードを利用する

ちょっと書くのが面倒なDBファサードの場合。
これでも取得はできるがちょっと長くなる。

\DB::table($post->favoriteUsers()->getTable())
    ->where($post->favoriteUsers()->getForeignPivotKeyName(), $post->id)
    ->pluck('user_id');
// select "user_id" from "favorite_post_users" where "post_id" = ?

newPivotを使ってみる

これはタイプヒントで出てきたのでやってみたのですが、たしかにモデルはつくられるもののPostモデルのidがwhereされませんでした。
これだとDBファサードでやるのと変わらないですね。。。

$post->favoriteUsers()->newPivot()->pluck('user_id');
// select "user_id" from "favorite_post_users"

newPivotQueryの場合

さて、実際に使えたnewPivotQueryは以下のようなソースになっておりました。
Laravel 7のコードから引っ張ってきたので今はもう少し違うかもしれない

public function newPivotQuery()
{
    $query = $this->newPivotStatement();

    foreach ($this->pivotWheres as $arguments) {
        $query->where(...$arguments);
    }

    foreach ($this->pivotWhereIns as $arguments) {
        $query->whereIn(...$arguments);
    }

    foreach ($this->pivotWhereNulls as $arguments) {
        $query->whereNull(...$arguments);
    }

    return $query->where($this->foreignPivotKey, $this->parent->{$this->parentKey});
}

一番重要なのが最後に書いてあるwhereで、$this->parent->{$this->parentKey}が書かれているのでリレーションを維持した状態で取得できるわけですね。

Discussion