4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravelの中間テーブル値を更新する

Posted at

目的

ユーザーがグループに所属する際に役割(role)を任意で設定できる
※roleはマスターでよくね?はひとまず放っておいて…

実行バージョンは Laravel 8.57.0

ドキュメントにもなかったので、書置きです!

構成例

Userテーブル

id email name
1 a@example.com tanaka
2 b@example.com saitou
3 a2@example.com chiba

Groupテーブル

id name
1 Aグループ
2 Bグループ

中間テーブル

id group_id user_id role
1 1 1 リーダー
2 2 2 グループ長

モデル

App\Models\User.php
use App\Models\Group;

class User extends Authenticatable
{

  ~諸々略~

  public function groups()
  {
    return $this->belongsToMany(Group::class);
  }
}

Groupモデルは割愛します。


結果だけで良い!って人は中間テーブルの値を更新(本題!)へ!

上記のような構成の場合、まず

中間テーブルへの追加(紐づける)

chibaさんをAグループサポーターにする時

$user_id = 3; // chiba
$group_id = 1; // Aグループ

User::find(3)->groups()->attach($group_id, ['role' => 'サポーター']);

で、このtanakaさんAグループサポーターという役割につくことができました。
昇格の時、経歴として考え、再度attachメソッドでも良いかもですが、今回は更新します。

updateExistingPivotメソッド

公式だと、
updateExistingPivotメソッドがぱっと目に入りました。
Eloquent:リレーション 8.x Laravel 中間テーブルのレコード更新
でも、既存のって書いてる…

横着したい…中間テーブル存在してなくても、作成して、存在してれば更新してほしい!!
ってので、見つけたのが

syncWithPivotValuesメソッド

Eloquent:リレーション 8.x Laravel 関連の同期
にある、syncWithPivotValuesメソッド

同期したモデルIDごとに同じ中間テーブルの値を挿入したい場合は、syncWithPivotValuesメソッドを使用できます。

落とし穴

上記の公式ドキュメントの参考ソース

$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);

この通り最初の構成を例に書き換えると

$user_id = 3; // chiba
$group_id = 1; // Aグループ

User::find(3)->groups()->syncWithPivotValues([$user_id], ['role' => 'サポーター']);

この結果…
tanakaさんが消えた!
リーダーがいなくなった!イェーイ!
なんてこった 責任取るやつ 旗振りがいないなんて!
第一引数で渡していない外部キー(この場合ユーザーのID)たちはdetachされる…
同期処理の中にあるからね…同期だからね(ショック)
同期系メソッドでも他を残せないかsyncWithoutDetachingメソッドみたいに!って思ったけど、
公式ドキュメントには載ってなかった…
とりあえず上記の各メソッドのソースコード実際に見てみよ…で見つけれたのが次!

※ちなみに、syncWithoutDetachingメソッドは外部キーのみ対応しているので、シンプルな中間テーブルの場合これで事足りるはず。

中間テーブルの値を更新(本題!)

他はそのままで(上の件に書いてます!)、対象のみ(今回はchibaさん)を
chibaさんをAグループサポーターからサブリーダーに更新する時

$user_id = 3; // chiba
$group_id = 1; // Aグループ

User::find(3)->groups()->syncWithPivotValues($group_id, ['role' => 'サブリーダー'], false);

結論だけ言うと、第3引数falseを利用するだけでした!
ドキュメントせめてデフォルト値あること書いてくれ!ってのと、改めてフレームワークの中身見るべきだなと…

※第1引数を配列でなくしているのは、下記の該当メソッド内で、collectメソッドが実行されているため、単一の場合、配列からのcollectionクラスとして処理されるためです。
project\vendor\laravel\framework\src\Illuminate\Collections\Traits\EnumeratesValues.phpgetArrayableItemsメソッドでその記述があります。

該当のメソッド

以下が該当のメソッド。
デフォルト値入ってますねー

\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable.php
    /**
     * Sync the intermediate tables with a list of IDs or collection of models with the given pivot values.
     *
     * @param  \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array  $ids
     * @param  array  $values
     * @param  bool  $detaching
     * @return array
     */
    public function syncWithPivotValues($ids, array $values, bool $detaching = true)
    {
        return $this->sync(collect($this->parseIds($ids))->mapWithKeys(function ($id) use ($values) {
            return [$id => $values];
        }), $detaching);
    }

で、このメソッド内のsyncはこちら
こっちでもデフォルト値入ってますねー

\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable.php
    /**
     * Sync the intermediate tables with a list of IDs or collection of models.
     *
     * @param  \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array  $ids
     * @param  bool  $detaching
     * @return array
     */
    public function sync($ids, $detaching = true)
    {
        $changes = [
            'attached' => [], 'detached' => [], 'updated' => [],
        ];

        // First we need to attach any of the associated models that are not currently
        // in this joining table. We'll spin through the given IDs, checking to see
        // if they exist in the array of current ones, and if not we will insert.
        $current = $this->getCurrentlyAttachedPivots()
                        ->pluck($this->relatedPivotKey)->all();

        $detach = array_diff($current, array_keys(
            $records = $this->formatRecordsList($this->parseIds($ids))
        ));

        // Next, we will take the differences of the currents and given IDs and detach
        // all of the entities that exist in the "current" array but are not in the
        // array of the new IDs given to the method which will complete the sync.
        if ($detaching && count($detach) > 0) {
            $this->detach($detach);

            $changes['detached'] = $this->castKeys($detach);
        }

        // Now we are finally ready to attach the new records. Note that we'll disable
        // touching until after the entire operation is complete so we don't fire a
        // ton of touch operations until we are totally done syncing the records.
        $changes = array_merge(
            $changes, $this->attachNew($records, $current, false)
        );

        // Once we have finished attaching or detaching the records, we will see if we
        // have done any attaching or detaching, and if we have we will touch these
        // relationships if they are configured to touch on any database updates.
        if (count($changes['attached']) ||
            count($changes['updated']) ||
            count($changes['detached'])) {
            $this->touchIfTouching();
        }

        return $changes;
    }

余談

syncWithoutDetachingメソッドは、syncメソッドの第2引数をfalseにして実行しているだけ

\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable.php
    /**
     * Sync the intermediate tables with a list of IDs without detaching.
     *
     * @param  \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array  $ids
     * @return array
     */
    public function syncWithoutDetaching($ids)
    {
        return $this->sync($ids, false);
    }
4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?