eloquent中如何优雅处理多对多关系写入一个关系表的操作

一、基础多对多关系操作

1. 定义多对多关系

// User 模型
class User extends Model {
    public function roles() {
        return $this->belongsToMany(Role::class);
    }
}

// Role 模型
class Role extends Model {
    public function users() {
        return $this->belongsToMany(User::class);
    }
}

2. 附加/分离关联

// 附加单个角色
$user->roles()->attach($roleId);

// 附加带额外字段
$user->roles()->attach($roleId, ['expires_at' => now()->addDays(30)]);

// 批量附加
$user->roles()->attach([1, 2, 3]);

// 分离角色
$user->roles()->detach($roleId);

// 同步角色(自动清理旧关联)
$user->roles()->sync([1, 2]);

二、中间表含额外字段

1. 声明中间表字段

class User extends Model {
    public function roles() {
        return $this->belongsToMany(Role::class)
            ->withPivot(['expires_at', 'assigned_by']); // 声明额外字段
    }
}

2. 操作带额外字段的关联

// 附加带额外数据
$user->roles()->attach(1, [
    'expires_at' => now()->addYear(),
    'assigned_by' => Auth::id(),
]);

// 更新中间表字段
$user->roles()->updateExistingPivot($roleId, [
    'expires_at' => now()->addMonth(),
]);

三、使用中间模型(推荐)

1. 创建中间模型

// 中间模型 RoleUser
class RoleUser extends Pivot {
    protected $table = 'role_user';
    protected $casts = ['expires_at' => 'datetime'];
}

2. 关联模型指定中间类

class User extends Model {
    public function roles() {
        return $this->belongsToMany(Role::class)
            ->using(RoleUser::class)
            ->withPivot(['expires_at']);
    }
}

3. 通过中间模型操作

// 创建关联记录
$user->roles()->attach($roleId, [
    'expires_at' => now()->addDays(30),
]);

// 查询中间模型数据
$roleUser = $user->roles()->where('role_id', 1)->first()->pivot;
echo $roleUser->expires_at;

// 直接操作中间模型
RoleUser::create([
    'user_id' => 1,
    'role_id' => 2,
    'expires_at' => now()->addWeek(),
]);

四、事务与批量操作

1. 使用数据库事务

DB::transaction(function () use ($user, $roles) {
    $user->roles()->detach();
    $user->roles()->attach($roles);
});

2. 批量同步带额外数据

$data = [
    1 => ['expires_at' => now()->addDays(30)],
    2 => ['expires_at' => now()->addDays(60)],
];

$user->roles()->sync($data);

五、自定义便捷方法

1. 在模型中封装方法

class User extends Model {
    // 给用户分配角色并记录分配人
    public function assignRole($roleId, $expiresDays = 30) {
        $this->roles()->attach($roleId, [
            'expires_at' => now()->addDays($expiresDays),
            'assigned_by' => Auth::id(),
        ]);
    }
}

// 使用
$user->assignRole(2, 60);

六、最佳实践建议

  1. 中间模型:当中间表有业务逻辑时,优先使用中间模型
  2. 批量操作:使用 sync 替代循环 attach/detach 提升性能
  3. 类型转换:在中间模型中定义 $casts 处理日期/JSON 等字段
  4. 事件监听:利用模型事件(如 pivotAttached)记录操作日志
User::roles()->pivotAttached(function ($user, $roleId, $attributes) {
    Log::info("用户 {$user->id} 被分配角色 {$roleId}", $attributes);
});

通过以上方式,可以既保持代码简洁性,又能处理复杂的中间表业务逻辑。

Was this helpful?

0 / 0

发表回复 0