Блог на Laravel 7, часть 11. Панель управления — назначение ролей и прав для пользователей
18.01.2021
Теги: Laravel • MySQL • PHP • Web-разработка • БазаДанных • Блог • Пользователь • ПраваДоступа • Практика • Фреймворк • ШаблонСайта
Теперь о правах и ролях пользователей. У нас уже есть форма редактирования пользователя — можем в нее добавить checkbox-ы, позволяющие выбрать права и роли. Еще один способ — создать отдельную страницу в панели управления, где можно будет назначать для пользователя права и роли. Не решил, какой способ лучше, так что реализуем оба — у нас же учебный проект.
Роли и права, первый вариант
Чтобы создать на форме checkbox-ы для назначения прав и ролей — в контроллере надо получить все права и роли и передать их в шаблон. Чтобы форма редактирования пользователя не получилось слишком большой — создадим два шаблона и подключим их в форме.
class UserController extends Controller { /** * Показывает форму для редактирования пользователя */ public function edit(User $user) { $allroles = Role::all(); $allperms = Permission::all(); return view( 'admin.user.edit', compact('user', 'allperms', 'allroles') ); } /** * Обновляет данные пользователя в базе данных */ public function update(Request $request, User $user) { /* * Проверяем данные формы */ $this->validator($request->all(), $user->id)->validate(); /* * Обновляем пользователя */ if ($request->change_password) { // если надо изменить пароль $request->merge(['password' => Hash::make($request->password)]); $user->update($request->all()); } else { $user->update($request->except('password')); } /* * Назначаем роли и права */ if (auth()->user()->hasPermAnyWay('assign-role')) { $user->roles()->sync($request->roles); } if (auth()->user()->hasPermAnyWay('assign-permission')) { $user->permissions()->sync($request->perms); } /* * Возвращаемся к списку */ return redirect() ->route('admin.user.index') ->with('success', 'Данные пользователя успешно обновлены'); } /* ... */ }
@extends('layout.admin', ['title' => 'Редактирование пользователя']) @section('content') <h1 class="mb-4">Редактирование пользователя</h1> <form method="post" action="{{ route('admin.user.update', ['user' => $user->id]) }}"> @csrf @method('PUT') <div class="form-group"> <input type="text" class="form-control" name="name" placeholder="Имя, Фамилия" required maxlength="255" value="{{ old('name') ?? $user->name }}"> </div> <div class="form-group"> <input type="email" class="form-control" name="email" placeholder="Адрес почты" required maxlength="255" value="{{ old('email') ?? $user->email }}"> </div> <div class="form-group form-check"> <input type="checkbox" class="form-check-input" name="change_password" id="change_password"> <label class="form-check-label" for="change_password"> Изменить пароль пользователя </label> </div> <div class="form-group"> <input type="text" class="form-control" name="password" maxlength="255" placeholder="Новый пароль" value=""> </div> <div class="form-group"> <input type="text" class="form-control" name="password_confirmation" maxlength="255" placeholder="Пароль еще раз" value=""> </div> @perm('assign-role') @include('admin.user.all-roles') @endperm @perm('assign-permission') @include('admin.user.all-perms') @endperm <div class="form-group"> <button type="submit" class="btn btn-success">Сохранить</button> </div> </form> @endsection
Шаблон resources/views/admin/user/all-roles.blade.php
для выбора ролей пользователя:
<h5>Роли</h5> <div class="form-group d-flex flex-wrap"> @php /* * Тут возможны такие варианты: * 1. Форма редактирования еще не отправлена, привязанные роли берем * из связи модели User с моделью Role через сводную таблицу * 2. Форма редактирования была отправлена, но были ошибки формы, * поэтому отмеченные админом checkbox-ы берем из old() */ $roles = $user->roles->keyBy('id')->keys()->toArray(); if (old('roles')) $roles = old('roles'); @endphp @foreach ($allroles as $item) @php $checked = in_array($item->id, $roles) @endphp <div class="form-check-inline w-25 mr-0"> <input class="form-check-input" type="checkbox" name="roles[]" id="role-id-{{ $item->id }}" value="{{ $item->id }}" @if($checked) checked @endif> <label class="form-check-label" for="role-id-{{ $item->id }}"> {{ $item->name }} </label> </div> @endforeach </div>
Шаблон resources/views/admin/user/all-perms.blade.php
для выбора прав пользователя:
<h5>Права</h5> <div class="form-group d-flex flex-wrap"> @php /* * Тут возможны такие варианты: * 1. Форма редактирования еще не отправлена, привязанные права берем * из связи модели User с моделью Permission через сводную таблицу * 2. Форма редактирования была отправлена, но были ошибки формы, * поэтому отмеченные админом checkbox-ы берем из old() */ $perms = $user->permissions->keyBy('id')->keys()->toArray(); if (old('perms')) $perms = old('perms'); @endphp @foreach ($allperms as $item) @php $checked = in_array($item->id, $perms) @endphp <div class="form-check-inline w-25 mr-0"> <input class="form-check-input" type="checkbox" name="perms[]" id="perm-id-{{ $item->id }}" value="{{ $item->id }}" @if($checked) checked @endif> <label class="form-check-label" for="perm-id-{{ $item->id }}"> {{ $item->name }} </label> </div> @endforeach </div>
Роли и права, второй вариант
Нам потребуется разрешить еще один маршрут для ресурсного контроллера — admin.user.show
, отвечать за который будет метод show()
.
/* * Панель управления: CRUD-операции над постами, категориями, тегами */ Route::group([ 'as' => 'admin.', // имя маршрута, например admin.index 'prefix' => 'admin', // префикс маршрута, например admin/index 'namespace' => 'Admin', // пространство имен контроллеров 'middleware' => ['auth'] // один или несколько посредников ], function () { /* * Просмотр и редактирование пользователей */ Route::resource('user', 'UserController', ['except' => [ 'create', 'store', 'destroy' /* раньше метод show() был запрещен */ ]]); });
class UserController extends Controller { /** * Показывает права и роли пользователя */ public function show(User $user) { $roles = Role::all(); $perms = Permission::all(); return view( 'admin.user.show', compact('user', 'roles', 'perms') ); } /* ... */ }
Шаблон для показа списка всех пользователей resources/views/admin/user/index.blade.php
будет содержать ссылку для перехода к управлению правами и ролями.
@extends('layout.admin', ['title' => 'Все пользователи']) @section('content') <h1 class="mb-4">Все пользователи</h1> <table class="table table-bordered"> <tr> <th>#</th> <th width="20%">Дата регистрации</th> <th width="25%">Имя, фамилия</th> <th width="20%">Адрес почты</th> <th width="15%">Публикаций</th> <th width="15%">Комментариев</th> <th><i class="fas fa-edit"></i></th> <th><i class="fas fa-user-cog"></i></th> </tr> @foreach($users as $user) <tr> <td>{{ $user->id }}</td> <td>{{ $user->created_at }}</td> <td>{{ $user->name }}</td> <td><a href="mailto:{{ $user->email }}">{{ $user->email }}</a></td> <td>{{ $user->posts->count() }}</td> <td>{{ $user->comments->count() }}</td> <td> @perm('edit-user') <a href="{{ route('admin.user.edit', ['user' => $user->id]) }}"> <i class="far fa-edit"></i> </a> @endperm </td> <td> <a href="{{ route('admin.user.show', ['user' => $user->id]) }}"> <i class="far fa-user-cog"></i> </a> </td> </tr> @endforeach </table> {{ $users->links() }} @endsection
Шаблон страницы resources/views/admin/user/show.blade.php
для просмотра и управления правами и ролями пользователя.
@extends('layout.admin', ['title' => 'Роли и права']) @section('content') <h1 class="mb-4">Роли и права</h1> <p>Пользователь: {{ $user->name }}</p> <h2>Роли</h2> <table class="table table-bordered"> <tr> <th width="10%">id</th> <th width="30%">Slug</th> <th width="30%">Name</th> <th width="30%">Assign/Unassign</th> </tr> @foreach($roles as $role) <tr> <td>{{ $role->id }}</td> <td>{{ $role->slug }}</td> <td>{{ $role->name }}</td> <td> @php $params = ['user' => $user->id, 'role' => $role->id]; @endphp @if ($user->hasRole($role->slug)) <a href="{{ route('admin.user.unassign.role', $params) }}" class="text-danger">Отнять эту роль</a> @else <a href="{{ route('admin.user.assign.role', $params) }}" class="text-success">Назначить эту роль</a> @endif </td> </tr> @endforeach </table> <h2>Права</h2> <table class="table table-bordered"> <tr> <th width="10%">id</th> <th width="30%">Slug</th> <th width="30%">Name</th> <th width="30%">Assign/Unassign</th> </tr> @foreach($perms as $perm) <tr> <td>{{ $perm->id }}</td> <td>{{ $perm->slug }}</td> <td>{{ $perm->name }}</td> <td> @php $params = ['user' => $user->id, 'perm' => $perm->id]; @endphp @if ($user->hasPerm($perm->slug)) <a href="{{ route('admin.user.unassign.perm', $params) }}" class="text-danger">Отнять это право</a> @else <a href="{{ route('admin.user.assign.perm', $params) }}" class="text-success">Назначить это право</a> @endif </td> </tr> @endforeach </table> @endsection
Для назначения и удаления права или роли нам потребуются еще четыре маршрута и четыре метода контроллера:
/* * Панель управления: CRUD-операции над постами, категориями, тегами */ Route::group([ 'as' => 'admin.', // имя маршрута, например admin.index 'prefix' => 'admin', // префикс маршрута, например admin/index 'namespace' => 'Admin', // пространство имен контроллеров 'middleware' => ['auth'] // один или несколько посредников ], function () { /* * Просмотр и редактирование пользователей */ Route::resource('user', 'UserController', ['except' => [ 'create', 'store', 'destroy' ]]); // доп.маршрут, чтобы назначить роль Route::get('user/{user}/role/{role}/assign', 'UserController@assignRole') ->name('user.assign.role'); // доп.маршрут, чтобы назначить право Route::get('user/{user}/perm/{perm}/assign', 'UserController@assignPerm') ->name('user.assign.perm'); // доп.маршрут, чтобы отнять роль Route::get('user/{user}/role/{role}/unassign', 'UserController@unassignRole') ->name('user.unassign.role'); // доп.маршрут, чтобы отнять право Route::get('user/{user}/perm/{perm}/unassign', 'UserController@unassignPerm') ->name('user.unassign.perm'); });
namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Permission; use App\Role; use App\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; class UserController extends Controller { /** * Показывает права и роли пользователя */ public function show(User $user) { $roles = Role::all(); $perms = Permission::all(); return view( 'admin.user.show', compact('user', 'roles', 'perms') ); } public function assignRole(User $user, Role $role) { if ( ! auth()->user()->hasPermAnyWay('assign-role')) { return redirect() ->route('admin.user.show', ['user' => $user->id]) ->withErrors('Нет прав на выполнение этого действия'); } $user->assignRoles($role->slug); return redirect() ->route('admin.user.show', ['user' => $user->id]) ->with('success', 'Данные пользователя успешно обновлены'); } public function assignPerm(User $user, Permission $perm) { if ( ! auth()->user()->hasPermAnyWay('assign-permission')) { return redirect() ->route('admin.user.show', ['user' => $user->id]) ->withErrors('Нет прав на выполнение этого действия'); } $user->assignPermissions($perm->slug); return redirect() ->route('admin.user.show', ['user' => $user->id]) ->with('success', 'Данные пользователя успешно обновлены'); } public function unassignRole(User $user, Role $role) { if ( ! auth()->user()->hasPermAnyWay('assign-role')) { return redirect() ->route('admin.user.show', ['user' => $user->id]) ->withErrors('Нет прав на выполнение этого действия'); } $user->unassignRoles($role->slug); return redirect() ->route('admin.user.show', ['user' => $user->id]) ->with('success', 'Данные пользователя успешно обновлены'); } public function unassignPerm(User $user, Permission $perm) { if ( ! auth()->user()->hasPermAnyWay('assign-permission')) { return redirect() ->route('admin.user.show', ['user' => $user->id]) ->withErrors('Нет прав на выполнение этого действия'); } $user->unassignPermissions($perm->slug); return redirect() ->route('admin.user.show', ['user' => $user->id]) ->with('success', 'Данные пользователя успешно обновлены'); } /* ... */ }
Создание, редактирование и удаление роли
Нам потребуется ресурсный контроллер для CRUD-операций над ролями, новые маршруты и несколько шаблонов для просмотра списка ролей, создания и редактирования роли.
Новые маршруты:
/* * Панель управления: CRUD-операции над постами, категориями, тегами */ Route::group([ 'as' => 'admin.', // имя маршрута, например admin.index 'prefix' => 'admin', // префикс маршрута, например admin/index 'namespace' => 'Admin', // пространство имен контроллеров 'middleware' => ['auth'] // один или несколько посредников ], function () { /* * CRUD-операции над ролями */ Route::resource('role', 'RoleController', ['except' => 'show']); });
Ресурсный контроллер:
> php artisan make:controller Admin/RoleController --resource --model=Role
namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Permission; use App\Role; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; class RoleController extends Controller { public function __construct() { $this->middleware('perm:manage-roles')->only('index'); $this->middleware('perm:create-role')->only(['create', 'store']); $this->middleware('perm:edit-role')->only(['edit', 'update']); $this->middleware('perm:delete-role')->only('destroy'); } /** * Показывает список всех ролей пользователя */ public function index() { $roles = Role::paginate(8); return view('admin.role.index', compact('roles')); } /** * Показывает форму для создания роли */ public function create() { $allperms = Permission::all(); return view('admin.role.create', compact('allperms')); } /** * Сохраняет новую роль в базу данных */ public function store(Request $request) { $this->validator($request->all(), null)->validate(); $role = Role::create($request->all()); $role->permissions()->attach($request->perms ?? []); return redirect() ->route('admin.role.index') ->with('success', 'Новая роль успешно создана'); } /** * Показывает форму для редактирования роли */ public function edit(Role $role) { $allperms = Permission::all(); return view('admin.role.edit', compact('role', 'allperms')); } /** * Обновляет роль в базе данных */ public function update(Request $request, Role $role) { if ($role->id === 1) { return redirect() ->route('admin.role.index') ->withErrors('Эту роль нельзя редактировать'); } $this->validator($request->all(), $role->id)->validate(); $role->update($request->all()); $role->permissions()->sync($request->perms ?? []); return redirect() ->route('admin.role.index') ->with('success', 'Роль была успешно отредактирована'); } /** * Удаляет роль из базы данных */ public function destroy(Role $role) { if ($role->id === 1) { return redirect() ->route('admin.role.index') ->withErrors('Эту роль нельзя удалить'); } $role->delete(); return redirect() ->route('admin.role.index') ->with('success', 'Роль была успешно удалена'); } /** * Возвращает объект валидатора с нужными правилами */ private function validator($data, $id) { $unique = 'unique:roles,slug'; if ($id) { // проверка на уникальность slug роли при редактировании, // исключая эту роль по идентифкатору в таблице БД roles $unique = 'unique:roles,slug,'.$id.',id'; } $rules = [ 'name' => [ 'required', 'string', 'max:50', ], 'slug' => [ 'required', 'max:50', $unique, 'regex:~^[-_a-z0-9]+$~i', ] ]; $messages = [ 'required' => 'Поле «:attribute» обязательно для заполнения', 'max' => 'Поле «:attribute» должно быть не больше :max символов', ]; $attributes = [ 'name' => 'Наименование', 'slug' => 'Идентификатор' ]; return Validator::make($data, $rules, $messages, $attributes); } }
Поскольку в контроллере мы используем «mass assigment» — добавим в модель Role
свойство $fillable
:
class Role extends Model { protected $fillable = [ 'name', 'slug', ]; /* ... */ }
Шаблон resources/views/admin/role/index.blade.php
для показа списка всех ролей пользователя:
@extends('layout.admin', ['title' => 'Все роли']) @section('content') <h1>Все роли</h1> @perm('create-role') <a href="{{ route('admin.role.create') }}" class="btn btn-success mb-4"> Создать роль </a> @endperm @if ($roles->count()) <table class="table table-bordered"> <tr> <th>#</th> <th width="45%">Идентификатор</th> <th width="45%">Наименование</th> <th><i class="fas fa-edit"></i></th> <th><i class="fas fa-trash-alt"></i></th> </tr> @foreach ($roles as $role) <tr> <td>{{ $role->id }}</td> <td>{{ $role->slug }}</td> <td>{{ $role->name }}</td> <td> @perm('edit-role') <a href="{{ route('admin.role.edit', ['role' => $role->id]) }}"> <i class="far fa-edit"></i> </a> @endperm </td> <td> @perm('delete-role') <form action="{{ route('admin.role.destroy', ['role' => $role->id]) }}" method="post" onsubmit="return confirm('Удалить эту роль?')"> @csrf @method('DELETE') <button type="submit" class="m-0 p-0 border-0 bg-transparent"> <i class="far fa-trash-alt text-danger"></i> </button> </form> @endperm </td> </tr> @endforeach </table> {{ $roles->links() }} @endif @endsection
Шаблон resources/views/admin/role/create.blade.php
для создания новой роли:
@extends('layout.admin', ['title' => 'Создание роли']) @section('content') <h1>Создание роли</h1> <form method="post" action="{{ route('admin.role.store') }}"> @csrf @include('admin.role.form') </form> @endsection
Шаблон resources/views/admin/role/edit.blade.php
для редактирования роли:
@extends('layout.admin', ['title' => 'Редактирование роли']) @section('content') <h1>Редактирование роли</h1> <form method="post" action="{{ route('admin.role.update', ['role' => $role->id]) }}"> @csrf @method('PUT') @include('admin.role.form') </form> @endsection
Шаблон resources/views/admin/role/form.blade.php
формы создания-редактирования:
<div class="form-group"> <input type="text" class="form-control" name="name" placeholder="Наименование" required maxlength="50" value="{{ old('name') ?? $role->name ?? '' }}"> </div> <div class="form-group"> <input type="text" class="form-control" name="slug" placeholder="Идентификатор" required maxlength="50" value="{{ old('slug') ?? $role->slug ?? '' }}"> </div> <h5>Права</h5> <div class="form-group d-flex flex-wrap"> @php // если это форма создания новой роли $perms = []; // если это форма редактирования роли if (isset($role)) $perms = $role->permissions->keyBy('id')->keys()->toArray(); // форма была отправлена, но с ошибками if (old('perms')) $perms = old('perms'); @endphp @foreach ($allperms as $item) @php $checked = in_array($item->id, $perms) @endphp <div class="form-check-inline w-25 mr-0"> <input class="form-check-input" type="checkbox" name="perms[]" id="perm-id-{{ $item->id }}" value="{{ $item->id }}" @if($checked) checked @endif> <label class="form-check-label" for="perm-id-{{ $item->id }}"> {{ $item->name }} </label> </div> @endforeach </div> <div class="form-group"> <button type="submit" class="btn btn-success">Сохранить</button> </div>
- Блог на Laravel 7, часть 10. Личный кабинет — CRUD-операции над постами и комментариями
- Блог на Laravel 7, часть 3. Checkbox «Запомнить меня» и подтверждение адреса почты
- Мини-блог на Laravel, часть 9. Защита маршрутов создания, редактирования и удаления
- Блог на Laravel 7, часть 17. Временная зона для пользователей, деплой на хостинг TimeWeb
- Блог на Laravel 7, часть 16. Роль нового пользователя, сообщение админу о новом посте
- Блог на Laravel 7, часть 12. Доп.страницы сайта в панели управления и в публичной части
- Блог на Laravel 7, часть 9. Панель управления — создание, публикация, удаление комментариев
Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Блог • Пользователь • Права доступа • Практика • Фреймворк • Шаблон сайта