LaravelテクニックのPivot

3237 ワード

リレーショナル・データベースで、パターンに一致する複数対のマルチテーブル関係を定義するには、2つのテーブルの関係として中間テーブルが必要です.Laravelではこのテーブルをpivotと呼び、関連するレコードをクエリーした後、pivotのプロパティを使用して関連テーブルのフィールドにアクセスできます.
$user = App\User::find(1);
foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

実際のアプリケーションでは、この中間テーブルには2つのテーブルの外部キーだけでなく、いくつかの追加のフィールドがあります.例を挙げます.
1人のユーザーは複数の部門に属することができます.すなわち、ユーザーと部門は多対多関係であり、1人のユーザーは異なる部門で役割が異なる可能性があります.つまり、ユーザーと役割も多対多です.この中間表の構造は以下の通りです.
+---------------+------------------+------+-----+---------+----------------+
| Field         | Type             | Null | Key | Default | Extra          |
+---------------+------------------+------+-----+---------+----------------+
| id            | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| user_id       | int(10) unsigned | NO   |     | NULL    |                |
| role_id       | int(10) unsigned | NO   |     | NULL    |                |
| department_id | int(10) unsigned | NO   |     | NULL    |                |
| created_at    | timestamp        | YES  |     | NULL    |                |
| updated_at    | timestamp        | YES  |     | NULL    |                |
+---------------+------------------+------+-----+---------+----------------+

すべての部門でユーザーが対応する役割を取得する場合:
foreach($user->departments as $department) {
    $role = Role::find($department->privot->role_id);
}

手順はやはり煩雑で、このpivotが他のModelのように$department->privot->roleでキャラクター情報を直接入手できれば便利です.
Laravelのコードを検討したところ、実装可能であることがわかりました.まずクラスを新規作成します.
namespace App\PivotModels;

use Illuminate\Database\Eloquent\Relations\Pivot;
use App\Models\Role;
use App\Models\Department;

class UserRole extends Pivot
{
    public function role()
    {
        return $this->belongsTo(Role::class);
    }
    public function department()
    {
        return $this->belongsTo(Department::class);
    }
}

次に、App\Models\DepartmentクラスでnewPivotメソッドを書き換えます.
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
    if ($parent instanceof User) {
        return new UserRole($parent, $attributes, $table, $exists);
    }
    return parent::newPivot($parent, $attributes, $table, $exists);
}
App\Models\Userクラスのdepartmentsメソッドを変更します.
public function departments()
{
    return $this->belongsToMany(Department::class, 'user_role', 'department_id', 'user_id')
        ->withPivot(['department_id', 'user_id', 'role_id']) //              
        ->withTimestamps();
}

この時tinkerでテストできます
$pivot = $user->departments()->first()->pivot; //    App\PivotModels\UserRole  
$pivot->role; //          
$pivot->department; //        

さらに、Illuminate\Database\Eloquent\Relations\Pivotというクラスは、実際にはIlluminate\Database\Eloquent\Modelクラスに継承されており、すなわちmutators機能によってgetter/setterをカスタマイズすることができる.(テストpivotはmodelの$appends/$withなどの属性をサポートせず、定義しても対応する動作はありませんが、loadメソッドで関連オブジェクトをロードできます).