Laravel】複合主キーを持つ中間テーブルへの値保存時のエラー: local.ERROR: Illegal offset type

Laravel laravel

はじめに

  • Laravelで複合主キーを持つ中間テーブルへの値保存時のエラーを解消する。
    • 今回グループにユーザーを新たに招待して、招待したユーザーが参加を承諾すると中間テーブルに参加状態が保存されるということを行いたい前提で進めます。

エラー内容

  • local.ERROR: Illegal offset type {"userId":4,"exception":"[object] (TypeError(code: 0): Illegal offset type at /app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:1125)

現状

  • ボタンを押すとグループに参加(ボタン押下でエラー)

sample.blade.php

<form action="{{ route('book.update_groupuser') }}" method="post">
    @csrf
    <button type="submit" name="participation_status" value="参加">参加</button>
</form>
  • コントローラー(save時にエラー)

BookController.php

    public function updateGroupUser(Request $request){
        $participation_status = $request->participation_status;

        if($participation_status === '参加'){
            $memo_groups = User::find(Auth::id())->memogroup;

            foreach ($memo_groups as $memo_group){

                if($memo_group->pivot->participation_status === '招待中'){
                    $group_user = GroupUser::where('user_id', Auth::id())->where('group_id', $memo_group->id)->first();
                    $group_user->participation_status = '参加';
                    $group_user->save(); // ここでエラー
                }
            }
        }

        return redirect()->route('book.index');
    }

原因

  • 調べてみるとLaravelのORMでは複合PKは推奨されておらず、保存できないっぽい?

解決策

  • モデルを複合主キーで適切に動作させるには、該当のModelファイル内で下記のTraitの特性を使って読み込む

sample.php

class SampleModel extends Model {
	
	use HasCompositePrimaryKeyTrait; // 追加

	protected $primaryKey = ['key1','key2']; // この設定も忘れずに

	public $incrementing = false; // この設定も忘れずに
}
  • Trait作成

HasCompositePrimaryKeyTrait.php

<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Builder;

trait HasCompositePrimaryKeyTrait{

  //Get the value indicating whether the IDs are incrementing.
  public function getIncrementing(){
      return false;
  }


  //Set the keys for a save update query.
  protected function setKeysForSaveQuery($query){ //edit Builder $query to $query

    foreach ($this->getKeyName() as $key) {
      // UPDATE: Added isset() per devflow's comment.
      if (isset($this->$key)){
        $query->where($key, '=', $this->$key);
      }else
        throw new Exception(__METHOD__ . 'Missing part of the primary key: ' . $key);
      }
      return $query;
  }


  protected function getKeyForSaveQuery($keyName = null)
{

    if(is_null($keyName)){
        $keyName = $this->getKeyName();
    }

    if (isset($this->original[$keyName])) {
        return $this->original[$keyName];
    }

    return $this->getAttribute($keyName);
}


   //Execute a query for a single record by ID.
   public static function find($ids, $columns = ['*']){
   $me = new self;
   $query = $me->newQuery();

   foreach ($me->getKeyName() as $key) {
     $query->where($key, '=', $ids[$key]);
   }

   return $query->first($columns);
 }

  • Traitファイルを作成せずに、useだけすると下記エラーが表示されず解決されないので注意。
    • Declaration of LaravelTreats\Model\Traits\HasCompositePrimaryKey::setKeysForSaveQuery(Illuminate\Database\Eloquent\Builder $query) must be compatible with Illuminate\Database\Eloquent\Model::setKeysForSaveQuery($query) {"userId":4,"exception":"[object] (Symfony\\Component\\ErrorHandler\\Error\\FatalError(code: 0): Declaration of LaravelTreats\\Model\\Traits\\HasCompositePrimaryKey::setKeysForSaveQuery(Illuminate\\Database\\Eloquent\\Builder $query) must be compatible with Illuminate\\Database\\Eloquent\\Model::setKeysForSaveQuery($query) at /app/app/Models/GroupUser.php:42)

結果

  • 無事saveメソッドで複合PKを使用していても、中間テーブルの値の保存ができるように。

終わりに

  • LaravelのORMで複合プライマリはなぜ推奨されていないの・・・ムズカシイ。

参考

https://qiita.com/embed-contents/link-card#qiita-embed-content__a480eac0d9f235894b557f0a80e121e6
https://qiita.com/embed-contents/link-card#qiita-embed-content__345e38bccbb02c438de72533ced34059

コメント

タイトルとURLをコピーしました