Laravel 技巧之 Pivot

leo發表於2017-02-11

在關係式資料庫中,要定義一個符合正規化的多對多表關係需要一箇中間表作為兩個表的關係。在Laravel中這個表稱為pivot,在查詢出關聯的記錄之後,可以透過pivot屬性來訪問關聯表的欄位:

$user = App\User::find(1);
foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

在實際應用中,這個中間表可能不僅僅包含兩個表的外來鍵,還有一些附加的欄位,舉個例子:

一個使用者可以屬於多個部門,即使用者和部門是多對多關係,一個使用者在不同部門裡角色可能不一樣,即使用者和角色也是多對多。這個中間表的結構如下:

+---------------+------------------+------+-----+---------+----------------+
| 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方法來載入關聯物件)。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章