はじめに
- 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で複合プライマリはなぜ推奨されていないの・・・ムズカシイ。
参考