【Laravel】HasManyThrough でリレーションの中間テーブルをまとめて更新する

  • 2020年12月25日
  • 2022年9月22日
  • Laravel

 Laravel には多対多を表現するHasManyThroughという仕組みがあり、これを利用して中間テーブルのレコードを他の具体的なモデル名を参照しなくとも操作できます。具体的には、次のメソッドをEloquentモデルに増やし、使うことで多対多リレーションの制御をどのモデルからでも楽にできる様になります。

    /**
     * HasManyThrough で使っているリレーションをまとめて更新するメソッド
     * @param  HasManyThrough       $hasManyThrough      Eloquentの多対多リレーションを表現したクラス
     * @param  array                $newThroughTgtIds    更新後のhasManyThroughで参照しているキー全て
     * @param  string|string[]|null $reloadRelationNames
     * @throws Exception
     */
    public function updateHasManyThroughRelations(HasManyThrough $hasManyThrough, array $newThroughTgtIds, $reloadRelationNames=null): void
    {
        // HasManyThrough クラスにはリレーションに関わるが情報が入っています。
        // これを利用してある一つの Eloquent モデルの中だけで中間テーブルのリレーションを追加、削除して多対多リレーションの状態を制御できます。

        /** @var \Illuminate\Database\Eloquent\Model $throughModel 中間テーブルの Eloquent モデル */
        $throughModel                        = $hasManyThrough->getParent();
        /** @var string $thisKeyInThroughModel 中間テーブルで使っている$thisのテーブルを参照しているカラムの名前 */
        $thisKeyInThroughModel               = $hasManyThrough->getFirstKeyName();
        /** @var string $hasManyThroughTgtKeyInThroughModel 中間テーブルで使っている HasManyThrogh で参照するテーブルを参照しているカラムの名前 */
        $hasManyThroughTgtKeyInThroughModel  = $hasManyThrough->getSecondLocalKeyName();

        /** @var Collection|int[]|string[] $alreadyExistThroughModelIds 既に$thisとリレーションを持っている中間テーブルのID */
        $alreadyExistThroughModelIds         = $hasManyThrough->getParent()->newQuery()
                ->where($thisKeyInThroughModel, $this->getKey())
                ->get()->pluck($hasManyThrough->getForeignKeyName());

        // 更新後のhasManyThroughで参照しているキー全ての中から既に存在しているリレーションのIDを除去して、追加が必要なHasManyThrough先のレコードのIDを得ます。
        collect($newThroughTgtIds)->whereNotIn(null, $alreadyExistThroughModelIds)
            ->each(
                function ($newRelateThroughTgtId) use ($thisKeyInThroughModel, $hasManyThroughTgtKeyInThroughModel, $throughModel) {
                    // 追加が必要な中間テーブルレコードを追加していきます。
                    // ここで HasManyThrough が元の情報を使うで特定のモデルのカラムやプロパティに依存せず処理を書けます。
                    $newThroughModel = new $throughModel();
                    $newThroughModel->$thisKeyInThroughModel = $this->getKey();
                    $newThroughModel->$hasManyThroughTgtKeyInThroughModel = $newRelateThroughTgtId;
                    $newThroughModel->saveOrFail();
                }
            );

        // 既に存在しているリレーションのIDから更新後のhasManyThroughで参照しているキー全ての中を除去して、削除が必要な中間テーブルレコードのIDを得ます。
        $deleteThroughModelIds = $alreadyExistThroughModelIds->whereNotIn(null, $newThroughTgtIds);

        // 削除が必要なレコードの Eloquent を まとめて削除
        $throughModel->newQuery()
            ->where($thisKeyInThroughModel, $this->getKey())
            ->whereIn(
                $throughModel->getTable().'.'.$hasManyThrough->getForeignKeyName(),
                $deleteThroughModelIds
            )->delete();

        // $thisがリレーションを既にロード済みの場合、データベースを変更してもリレーションが変更されません。
        // load メソッドで明示的にリレーションをロードすることで再読み込みしてリレーションをデータベースに即した形にできます。
        $reloadRelationNames && $this->load($reloadRelationNames);
    }
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG