Большинству из нас не нужна сложная система управления доступов в наших приложениях. Мы хотим простую и гибкую систему, без излишеств. Что мы подразумеваем под «простой» системой? Допустим у вас в проекте есть команды, у команд есть владелец и участники. Мы хотим знать: кто владелец, у кого есть раширенные права, а кто простой член команды.
Говоря на языке «кода», это значит что у нас есть модели User и Team, а так же промежуточная таблица, которая их объеденяет. Далее я покажу вам просто вариант подобной связи.
Промежуточная таблица
Структура промежуточной таблицы проста и понятна. В общем случае она выглядит примерно так:
Schema::create('team_user', function (Blueprint $table) {
$table->unsignedInteger('team_id')->index();
$table->unsignedInteger('user_id')->index();
$table->string('role')->default('member');
$table->unique(['team_id', 'user_id']);
// Foreign constraints, timestamps etc..
});
Как вы заметили, кроме стандартных полей, мы так же сохраняем аттрибут role
в нашей таблице.
Приемущество такого подхода в том, что пользователь можен одноврменно состоять в разных командах, и занимать в них разные роли.
Настраиваем Eloquent связи
Наш тип связи называется "many-to-many
". Код ниже говорит Laravel о том, что мы хотим использовать many-to-many
связь под названием team
:
{
return $this->belongsToMany(Team::class, 'team_user')
->using(Role::class)
->as('role')
->withPivot('role');
}
Это стандартное объявление belongsToMany()
связи, в котором мы так же запрашиваем дополнительные поля из связующей таблицы с помощью withPivot()
метода.
Нам требуется использовать withPivot() метод, так как мы хотим получать дополнительные поля из связующей таблицы. При вызове этого метода вы можете передать массив, в котором указать все дополнительные поля которые вам необходимы.
Кроме стандартных методов, мы используем еще две не совсем стандартные вещи. Первая: использование метода using()
, который говорит Laravel о том, что мы хотим использовать модель Role
для нашей связи, вместо стандартного класса Illuminate\Database\Eloquent\Relations\Pivot
.
Вторая вещь: использование as()
метода, который позволяет переименовать стандартный ключ pivot. Т.е теперь мы можем делать так:
// default
$user->team->pivot;
// with the as('role')
$user->team->role;
Такой подход делает код более понятным и читаемым.
Добавление пользователя в команду
После того, как мы объявили связь, мы можем добавить пользователя в команду. Обратите внимание что мы объявили связь только со стороны User, но если вы хотите сделать обратную связь (со стороны Team), вы можете просто скопировать код выше в класс Team и заменить название модели.
И теперь, если верить документации, мы можем делать так:
// the user is an owner
$user->teams()->attach(1, ['role' => 'owner']);
// omit the role to use the default "member"
$user->teams()->attach(1);
Просто добавляя еще один параметр к вызову attach
, мы можем указать роль пользователя.
Генерация модели для связующей таблицы
Мало кто знает, но в Laravel можно сгенерировать модель для связующей таблицы с помощью Artisan! Все что необходимо сделать, это вызывать команду make:model
с параметром -p
:
$ php artisan make:model Role -p
И теперь мы можем делать все что захотим. Мы можем работать с этой моделью, как с обычной Eloquent моделью.
Добавляем функционал в нашу pivot модель
Добавим в нашу Role модель проверку состоит ли пользователь в одной из групп:
public function hasPermission($roles)
{
return in_array($this->role, (array) $roles);
}
Использовать этот медот можно например так:
$user->teams->first()->role->hasPermission('owner'); // false
$user->teams->first()->role->hasPermission(['owner', 'member']); // true
Самое замечательное, что теперь мы можем вынести логику проверки прав например в Middleware, чтобы ограничить доступ к определенным маршрутам только пользователям у которых есть соотвествующая роль.
Так же мы можем легко добавить перевод для роли пользователя:
protected $appends = [
'label',
];
public function getLabelAttribute()
{
return trans('roles'.$this->role);
}
Заключение
Думаю данное решение покрывает запросы большинства разработчиков. Конечно вы можете его усложнять и расширять, и даже вынести его в отдельную библиотеку.