В этой части мы добавим регистрацию и авторизацию, а так же инструмент для присвоения ролей пользователям. Авторизацию и регистрацию мы реализуем практически по инструкции с официального сайта, но с небольшими дополнениями. Для начала давайте создадим основной макет (из корня проекта)
1 2 |
$ mkdir resources/views/layout $ touch resources/views/layout/default.blade.php |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>ACL</title> <link rel="stylesheet" href="/vendor/bootstrap/dist/css/bootstrap.min.css"> </head> <body> <div class="container"> <nav class="navbar navbar-default" role="navigation"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Home</a> </div> <div class="collapse navbar-collapse navbar-ex1-collapse"> <ul class="nav navbar-nav navbar-right"> @if(\Auth::check()) <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{!! Auth::user()->name !!} <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="{!! action('Auth\AuthController@getLogout') !!}">Выход</a></li> </ul> </li> @else <li {!! \Route::currentRouteAction() == "App\Http\Controllers\Auth\AuthController@getLogin" ? 'class="active"' : '' !!}><a href="{!! action('Auth\AuthController@getLogin') !!}">Вход</a></li> <li {!! \Route::currentRouteAction() == "App\Http\Controllers\Auth\AuthController@getRegister" ? 'class="active"' : '' !!}><a href="{!! action('Auth\AuthController@getRegister') !!}">Регистрация</a></li> @endif </ul> </div> </nav> @yield('content') </div> {{urvanov-syntax-highlighter-internal:0}} {{urvanov-syntax-highlighter-internal:1}} @yield('scripts') </body> </html> |
Поправим шаблон главной страницы на использование единого макета который мы только что создали. Откроем файл /resources/view/welcome.blade.php и заменим содержимое на
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@extends('layout.default') @section('content') <div class="jumbotron"> <div class="container"> <h1>Hello, world!</h1> <p>Contents...</p> <p> <a class="btn btn-primary btn-lg">Learn more</a> </p> </div> </div> @stop |
Хотя можете там что-то свое собрать.
Теперь давайте создадим представления для авторизации и регистрации. Для этого нам надо создать директорию для этих представлений (можно конечно не использовать директории, но я предпочитаю структурировать данные)
1 |
mkdir resources/views/auth |
Создадим файл представления для регистрации touch resources/views/auth/register.blade.php с содержимым
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
@extends('layout.default') @section('content') <div class="col-md-6 col-md-offset-3"> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Регистрация</h3> </div> <div class="panel-body"> <form method="POST" action="{!! action('Auth\AuthController@postRegister') !!}"> {!! csrf_field() !!} <div class="form-group {!! !$errors->has('name') ?: 'has-error' !!}"> <label for="name">Имя</label> <input type="text" class="form-control" name="name" value="{{ old('name') }}"> </div> <div class="form-group {!! !$errors->has('email') ?: 'has-error' !!}"> <label for="email">Эл.почта</label> <input type="email" class="form-control" name="email" value="{{ old('email') }}"> </div> <div class="row"> <div class="form-group col-md-6 {!! !$errors->has('password') ?: 'has-error' !!}"> <label for="password">Пароль</label> <input type="password" class="form-control" name="password" id="password"> </div> <div class="form-group col-md-6 {!! !$errors->has('password_confirmation') ?: 'has-error' !!}"> <label for="password_confirmation">Подтверждение пароля</label> <input type="password" class="form-control" name="password_confirmation" id="password_confirmation"> </div> </div> @if($errors->count()) <div class="alert alert-danger"> @foreach($errors->all() as $message) <div>{!! $message !!}</div> @endforeach </div> @endif <div class="text-right"> <button class="btn btn-primary" type="submit">Зарегистрироваться</button> <a class="btn btn-link" href="{!! action('Auth\AuthController@getLogin') !!}">Авторизация</a> </div> </form> </div> </div> </div> @stop |
и представление с формой авторизации touch resources/views/auth/login.blade.php с содержимым
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
@extends('layout.default') @section('content') <div class="col-md-6 col-md-offset-3"> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Авторизация</h3> </div> <div class="panel-body"> <form action="{!! action('Auth\AuthController@postLogin') !!}" method="POST" role="form"> {!! csrf_field() !!} <div class="form-group"> <label for="email">Эл.почта</label> <input type="email" class="form-control" name="email" value="{{ old('email') }}"> </div> <div class="form-group"> <label for="password">Пароль</label> <input type="password" class="form-control" name="password" id="password"> </div> <div class="checkbox"> <label for="remember"><input type="checkbox" id="remember" name="remember"> Запомнить меня</label> </div> @if($errors->count()) <div class="alert alert-danger"> @foreach($errors->all() as $message) <div>{!! $message !!}</div> @endforeach </div> @endif <div class="text-right"> <button type="submit" class="btn btn-primary">Вход</button> <a class="btn btn-link" href="{!! action('Auth\AuthController@getRegister') !!}">Регистрация</a> </div> </form> </div> </div> </div> @stop |
И что бы все это заработало надо добавить маршруты в app/Http/routes.php
1 2 3 4 5 6 7 8 |
// Authentication routes... Route::get('auth/login', 'Auth\AuthController@getLogin'); Route::post('auth/login', 'Auth\AuthController@postLogin'); Route::get('auth/logout', 'Auth\AuthController@getLogout'); // Registration routes... Route::get('auth/register', 'Auth\AuthController@getRegister'); Route::post('auth/register', 'Auth\AuthController@postRegister'); |
Регистрация и авторизация готова, но есть одно небольшое неприятное «но». После прохождения процедуры регистрации, пользователя будет перенаправлять по адресу /home, что будет вызывать 404 ошибку. Если учесть, что по умолчанию при успешной регистрации пользователь автоматически авторизуется, то на мой взгляд, лучше сделать перенаправление на главную страницу. Для этого в контроллере app/Http/Controllers/Auth/AuthController.php необходимо добавить свойство
1 |
protected $redirectPath = '/'; |
Теперь после регистрации и авторизации пользователя будет перенаправлять на главную страницу.
Пришла пора создать UserController и представления для него. Собственно мы уже потихонечку подбираемся к финалу. И так! Создадим контроллер для работы с профилями пользователей
1 |
php artisan make:controller UserController |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
<?php namespace App\Http\Controllers; use Gate; use App\User; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; use App\Http\Requests; use App\Http\Controllers; class UserController extends Controller { public function __construct() { Gate::define('role.assign', function ($user) { if ($user->allowRules('*')) return true; }); Gate::define('user.edit', function ($user, $usr) { if ($user->allowRules('*')) return true; return $user->id === $usr->id; }); } /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { return view('user.list')->withUsers(User::all()); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { try { return view('user.show')->withUser(User::find($id)); } catch (ModelNotFoundException $e) { return abort(404); } } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { try { $user = User::findOrFail($id); if (Gate::denies('user.edit', $user)) return abort(403); } catch (ModelNotFoundException $e) { return abort(404); } return view('user.edit') ->withUser($user); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { try { $user = User::findOrFail($id); if (\Gate::denies('user.edit', $user)) return abort(403); } catch (ModelNotFoundException $e) { return abort(404); } // обновляем профиль пользователя $user->fill($request->all())->save(); // Удаляем все присвоенные роли у пользователя $user->roles()->detach(); // если иммеются назнаеченные роли if (count($request->input('roles'))) // для оптимизации запросов выполняем добавление ролей в одну транзакцию \DB::transaction(function () use ($request, $user) { foreach ($request->input('roles') as $role_id) $user->roles()->attach($role_id); }); return redirect()->route('user.index'); } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { // } } |
Как вы наверно заметили в конструкторе контроллера мы используем Gate. Вот в нем то вся изюминка и заключается.
Не буду тут объяснять как работает Gate а просто оставлю ссылку на документацию (в переводе).
Добавим три представления для UserController
Список пользователей
1 |
touch resources/views/user/list.blade.php |
1 2 3 4 5 6 7 8 9 10 11 12 |
@extends('layout.default') @section('content') <ul class="list-group"> @foreach($users as $user) <li class="list-group-item"> <h4><a href="{!! route('user.show', $user->id) !!}">{!! $user->name !!}</a> ({!! $user->email !!})</h4> @can('user.edit', $user)<small><a href="{!! route('user.edit', $user->id) !!}">Редактировать</a></small>@endcan </li> @endforeach </ul> @stop |
Просмотр профиля
1 |
touch resources/views/user/show.blade.php |
1 2 3 4 5 |
@extends('layout.default') @section('content') <h2>{!! $user->name !!}</h2> @stop |
Я сделал минимальный вариант
Редактирования профиля пользователя
1 |
touch resources/views/user/edit.blade.php |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
@extends('layout.default') @section('content') <form action="{!! route('user.update', $user->id) !!}" method="POST" role="form"> {!! csrf_field() !!}{!! method_field('put') !!} <legend>Редактирование профиля</legend> <div class="row"> <div class="col-md-6"> <div class="form-group"> <label for="name">Имя</label> <input type="text" class="form-control" name="name" id="name" value="{!! $user->name !!}"> </div> <div class="form-group"> <label for="email">Эл.почта</label> <input type="text" class="form-control" name="email" id="email" value="{!! $user->email !!}"> </div> </div> @can('role.assign') <div class="col-md-6"> <div><label>Роли</label></div> @foreach(Bitw\Acl\Models\Role::all() as $role) <div class="checkbox"> <label for="role{!! $role->id !!}"><input type="checkbox" id="role{!! $role->id !!}" name="roles[]" value="{!! $role->id !!}" {!! !$user->hasRole($role) ?: 'checked' !!}>{!! $role->name !!}</label> </div> @endforeach </div> @endcan </div> <div class="row"> <div class="col-md-6 text-right"> <button type="submit" class="btn btn-primary">Сохранить</button> <a class="btn btn-link" href="{!! route('user.index') !!}">Назад</a> </div> </div> </form> @stop |
И чтобы все это заработало необходимо добавить маршрут в app\Http\routes.php
1 |
Route::resource('user', 'UserController'); |
В конструкторе UserController вы наверняка заметили $user->allowRules(‘имя_правила’), да даже если не разглядывали код то при выполнении у вас в этом месте будет ошибка. Все потому что, модель User надо включить примесь
1 |
\Bitw\Acl\Models\User |
В прошлой части мы создали модель Role и теперь нам надо её доработать.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<?php namespace Bitw\Acl\Models; use App\User as AppUser; use Illuminate\Database\Eloquent\Model; class Role extends Model implements RoleInterface { protected $table = 'roles'; public $timestamps = false; protected $fillable = ['name', 'description', 'rules']; /** * The Users relationship. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function users() { return $this->belongsToMany(AppUser::class, 'users_roles', 'role_id', 'user_id'); } /** * {@inheritDoc} */ public function getRoleId() { return $this->getKey(); } /** * {@inheritDoc} */ public function getRoleName() { return $this->name; } /** * {@inheritDoc} */ public function getUsers() { return $this->users; } /** * {@inheritDoc} */ public static function getUsersModel() { return static::$usersModel; } /** * {@inheritDoc} */ public static function setUsersModel($usersModel) { static::$usersModel = $usersModel; } } |
Интерфейс который мы задействовали в данной модели
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?php namespace Bitw\Acl\Models; interface RoleInterface { /** * Returns the role's primary key. * * @return int */ public function getRoleId(); /** * Returns all users for the role. * * @return \IteratorAggregate */ public function getUsers(); /** * Returns the users model. * * @return string */ public static function getUsersModel(); /** * Sets the users model. * * @param string $usersModel * @return void */ public static function setUsersModel($usersModel); } |
Ну как бы на этом все. Для удобства обработки некоторых моментов можно добавить middleware Access
1 |
php artisan make:middleware Access |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Auth\Guard; class Access { /** * The Guard implementation. * * @var Guard */ protected $auth; /** * Create a new filter instance. * * @param Guard $auth * @return void */ public function __construct(Guard $auth) { $this->auth = $auth; } /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if ($this->auth->check()) { if ($this->auth->user()->allowRoutes($request->route()->getName())) { return $next($request); } return response('Forbidden.', 403); } return $next($request); } } |
и теперь при необходимости можно использовать его в маршрутах или конструкторах контроллеров.
На этом все. Если будут вопросы и пожелания то будет продолжение
Исходники если кому надо https://github.com/bitw/acl-example