ユーザー管理の実装からの続きです。
ロール管理機能を追加していきます。
目次
ロール管理画面の概要
機能
ロール管理画面も、基本的にはユーザー管理と同じように作成します。必要な機能は以下の通りです。
- 新しいロールの作成
- ロール名
- slug
- 追加ボタン
- 新しい権限の追加
- 権限の文字列のみ
- 追加ボタン
- ロール一覧
- ロール名
- slug
- 権限リスト(全ての権限を並べたチェックボックス)
- 修正ボタン
- 削除ボタン
アクセス権限
ユーザーが持つrole.view, role.create, role.update, role.delete の権限でチェックします。
ロール管理用のコントローラーの作成
ユーザー管理の時と同様に、resourceフラグでコントローラーを作成して、ルートに適用します。
ロール用のコントローラーを作成
- ターミナルから、以下を実行して、ロール用のリソースコントローラーを作成
php artisan make:controller Sentinel/RoleController --resource
パーミッション用のコントローラーを作成
パーミッションは、追加と削除のみなので、手動でコントローラーとルートを作成します。
- app/Http/Controllers/Sentinel/PermissionController.php を新規に作成してエディターで開く
- 以下のコードを追加
<?php namespace App\Http\Controllers\Sentinel; use Illuminate\Http\Request; use App\Http\Requests; use App\Http\Controllers\Controller; class PermissionController extends Controller { /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { // } }
ルートを設定
作成したコントローラーへのアクセスをルートに追加します。
- app/Http/routes.php をエディターで開く
- 以下の行をusersの定義の下に追加して、ロールのためのルートを追加
Route::resource('roles', 'Sentinel\RoleController'); Route::post('permissions', 'Sentinel\PermissionController@store'); Route::delete('permissions/{name}', 'Sentinel\PermissionController@destroy');
ロール管理画面の開発
ビューを表示するためのコードを作成
ロール管理画面用のビューを呼び出すルートを追加します。
- app/Http/Controllers/Sentinel/RoleController.php をエディターで開く
- あらかじめ作成されているindex()メソッドを、以下のようにコード追加する
public function index()
{
return view('sentinel.roles', ['permissions' => self::getPermissionList()]);
}
パーミッション一覧を取得するためのコードを追加
パーミッション一覧を表示するために、現在登録されている全てのロールが持っている、全てのパーミッション名のリストが必要なので、それを取得するための関数を作成します。
- 引き続き、app/Http/Controllers/Sentinel/RoleController.php で作業する
- Sentinelを参照するために、コードの上の方に以下を追加する
use Sentinel;
- RoleControllerクラスに以下の関数を追加する
/** * 全ロールに設定されているパーミッションのリストを作成 */ public static function getPermissionList() { $permissions = []; foreach(Sentinel::getRoleRepository()->all() as $role) { foreach($role->permissions as $k => $v) { if (!in_array($k, $permissions)) { $permissions[] = $k; } } } return $permissions; }
ビューの作成
ビューを作成します。パーミッションは単純な追加と削除のみなので、今回はロールとパーミッションの両方をまとめて管理するページにします。
エラー表示ブロックの共有化
エラーが発生した要素ごとにエラーメッセージを表示させようと思います。各要素で必要なコードは共通ですので、共通のビューを作成して、includeで読み込ませて使えるようにします。
- resources/views/parts/error-block.blade.php ファイルを作成して、エディターで開く
- 以下のコードを入力
@if ($errors->has($name)) <span class="help-block"> <strong>{{ $errors->first($name) }}</strong> </span> @endif
これを使うには、入力要素を以下のような形にして、inputタグに続けて@includeでerror-blockを読み込み、引数としてinputタグのnameを指定します。
*** 使用例
<div class="form-group{{ $errors->has('入力要素のname') ? ' has-error' : '' }}"> <input name="入力要素のname" class="form-control" ・・・ @include('parts.error-block', ['name' => '入力要素のname']) </div>
モーダルウィンドウの作成
ロールの変更や削除の選択をする時のBootstrapのモーダルウィンドウ用のビューを作成します。
- resources/views/parts/modal.blade.php を作成して、以下のコードを入力する
{{-- 以下のように呼び出しボタンを用意する <button type="button" class="btn" data-toggle="modal" data-target="#ターゲット"> ボタン名 </button> id 呼び出すID title モーダルに表示するタイトル body モーダルの本文 action 実行時のURL method 実行時のメソッド --}} <div class="modal fade" id="{{$id}}" tabindex="-1" role="dialog"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> <h4 class="modal-title">{{$title}}</h4> </div> <div class="modal-body"> {{$body}} </div> <div class="modal-footer"> <form action="{{$action}}" method="POST"> {{ csrf_field() }} {{ method_field($method) }} <button type="submit" class="btn btn-primary"> <i class="fa fa-btn fa-check"></i> はい </button> <button type="button" class="btn btn-default" data-dismiss="modal"> <i class="fa fa-btn fa-close"></i>いいえ</button> </form> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal -->
ボタンごとにFormがないものも用意します。
- resources/views/parts/modal-no-form.blade.php を作成して、以下のコードを入力する
{{-- モーダルのform未組み込み版 以下のように呼び出しボタンを用意する <button type="button" class="btn" data-toggle="modal" data-target="#ターゲット"> ボタン名 </button> id 呼び出すID title モーダルに表示するタイトル body モーダルの本文 --}} <div class="modal fade" id="{{$id}}" tabindex="-1" role="dialog"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> <h4 class="modal-title">{{$title}}</h4> </div> <div class="modal-body"> {{$body}} </div> <div class="modal-footer"> <button type="submit" class="btn btn-primary"> <i class="fa fa-btn fa-check"></i> はい </button> <button type="button" class="btn btn-default" data-dismiss="modal"> <i class="fa fa-btn fa-close"></i>いいえ </button> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal -->
ロール管理ビューの作成
- resources/views/sentinel/roles.blade.php ファイルを作成して、エディターで開く
- 以下のコードを入力
@extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-12"> @include('parts.info') @include('common.errors') <h4>パーミッション</h4> <table class="table table-striped table-hover table-bordered"> <thead> <tr> <th>パーミッション名</th> <th>操作</th> </tr> </thead> <tbody> <!-- 新規登録--> <form class="col" role="form" method="POST" action="{{url('permissions')}}"> <tr> {{ csrf_field() }} <td> <div class="form-group{{ $errors->has('new_permission') ? ' has-error' : '' }}"> <input id="new_permission" type="text" class="form-control" name="new_permission" value="{{ old('new_permission') }}"> @include('parts.error-block', ['name' => 'new_permission']) </div> </td> <td> <button type="submit" class="btn btn-primary"> <i class="fa fa-btn fa-plus"></i> 新規登録 </button> </td> </tr> </form> </tbody> </table> <h4>パーミッションの削除</h4> <div class="row"> <div class="col-md-12"> @foreach($permissions as $permission) <button type="button" class="btn btn-default" aria-label="Close" data-toggle="modal" data-target="#delete-permission-{{array_search($permission, $permissions)}}"> <i class="fa fa-btn fa-remove"></i> {{$permission}} </button> @include('parts.modal', [ 'id' => 'delete-permission-'.array_search($permission, $permissions), 'title' => trans('sentinel.delete_permission_title'), 'body' => trans('sentinel.confirm_delete_permission').' : '.$permission, 'action' => url('permissions', base64_encode($permission)), 'method' => 'DELETE' ]) @endforeach </div> </div> <hr> <h4>ロール追加</h4> <table class="table table-striped table-hover table-bordered"> <thead> <tr> <th>ロール名(日本語可)</th> <th>Slug</th> <th>パーミッション</th> <th>操作</th> </tr> </thead> <tbody> <!-- 新規登録--> <form class="col" role="form" method="POST" action="{{url('roles')}}"> {{ csrf_field() }} <tr> <td> <div class="form-group{{ $errors->has('new_role') ? ' has-error' : '' }}"> <input id="new_role" type="text" class="form-control" name="new_role" value="{{ old('new_role') }}"> @include('parts.error-block', ['name' => 'new_role']) </div> </td> <td> <div class="form-group{{ $errors->has('new_slug') ? ' has-error' : '' }}"> <input id="new_slug" type="text" class="form-control" name="new_slug" value="{{ old('new_slug') }}"> @include('parts.error-block', ['name' => 'new_slug']) </div> </td> <td> @foreach ($permissions as $per) <div> <input type="checkbox" name="new_per_{{str_replace(".", "-", $per)}}" {{old("new_per_".str_replace(".", "-", $per))=="on" ? 'checked="true"' : ''}} > {{$per}} </div> @endforeach </td> <td> <button type="submit" class="btn btn-primary"> <i class="fa fa-btn fa-plus"></i> 新規登録 </button> </td> </tr> </div> </form> </tbody> </table> <h4>ロール一覧</h4> <table class="table table-striped table-hover table-bordered"> <thead> <tr> <th>ロール名(日本語可)</th> <th>Slug</th> <th>パーミッション</th> <th colspan="2">操作</th> </tr> </thead> <tbody> @foreach(Sentinel::getRoleRepository()->all() as $role) <tr> <form class="col" role="form" method="POST" action="{{url('roles', $role->id)}}"> {{ csrf_field() }} {{ method_field('PUT') }} <td> <div class="form-group{{ $errors->has('role_'.$role->id.'_name') ? ' has-error' : '' }}"> <input type="text" class="form-control" name="role_{{$role->id}}_name" id="role_{{$role->id}}_name" value="{{empty(old('role_'.$role->id.'_name')) ? $role->name : old('role_'.$role->id.'_name')}}"> @include('parts.error-block', ['name' => 'role_'.$role->id.'_name']) </div> </td> <td> <div class="form-group{{ $errors->has('role_'.$role->id.'_slug') ? ' has-error' : '' }}"> <input type="text" class="form_control" name="role_{{$role->id}}_slug" id="role_{{$role->id}}_slug" value="{{empty(old('role_'.$role->id.'_slug')) ? $role->slug : old('role_'.$role->id.'_slug')}}"> @include('parts.error-block', ['name' => 'role_'.$role->id.'_slug']) </div> </td> <td> @foreach ($permissions as $per) <div> <input type="checkbox" name="role_{{$role->id}}_per_{{str_replace(".", "-", $per)}}" @if (old("role_".$role->id."_per_".str_replace(".", "-", $per))=="on") checked="true" @elseif ($role->hasAccess($per)) checked="true" @endif > {{$per}} </div> @endforeach </td> <td> <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#role_{{$role->id}}_update"> <i class="fa fa-btn fa-refresh"></i> 変更 </button> @include('parts.modal-no-form', [ 'id' => 'role_'.$role->id.'_update', 'title' => 'ロールの更新', 'body' => 'ロール['.$role->name.']の情報を更新しますか?', ]) </td> </form> <td> <button type="submit" class="btn btn-danger" data-toggle="modal" data-target="#role-{{$role->id}}-delete"> <i class="fa fa-btn fa-remove"></i> 削除 </button> @include('parts.modal', [ 'id' => 'role-'.$role->id.'-delete', 'title' => 'ロールの削除', 'body' => 'ロール['.$role->name.']を削除しますか?', 'action' => url('roles', $role->id), 'method' => 'DELETE', ]) </td> </tr> @endforeach </tbody> </table> </div> </div> </div> @endsection
以上で、ロール管理画面のフォームの表示まで出来ました。 http://0.0.0.0:8080/roles にアクセスしてみてください。画面が表示されて、利用されているパーミッションリストやロール一覧が確認できます。
まだ、ボタンの処理は実装していないので、何かを押してもエラーが発生します。それぞれの機能を追加していきます。
パーミッションの新規登録処理
ロールに設定できるパーミッションの種類を追加する処理を追加するために、 PermissionController.php のstore関数にコードを追加します。
- app/Http/Controllers/Sentinel/PermissionController.php をエディターで開く
- ロールコントローラーやSentinel、リダイレクトを利用するために以下の3つのuseを最初の方に追加
use App\Http\Controllers\Sentinel\RoleController; use Sentinel; use Redirect;
- 先に作成したstore関数を以下のように中身を実装する
public function store(Request $request) { // バリデーション $this->validate($request, [ // nameは必須で、255文字まで 'new_permission' => 'required|max:255', ]); // 既存なら何もしない $nowper = RoleController::getPermissionList(); if (in_array($request->new_permission, $nowper)) { // すでにあるので、エラーで返す return Redirect::back()->withInput()->withErrors(['new_permission' => trans('sentinel.same_permission')]); } // 作成実行 foreach(Sentinel::getRoleRepository()->all() as $role) { $role->addPermission($request->new_permission, false)->save(); } // 成功 return Redirect::back()->with(['info' => trans('sentinel.permission_add_done').":".$request->new_permission]); }
以上で、パーミッションの追加ができるようになりました。ロール管理画面を表示して、「test」などの名前のパーミッションの登録をしてみてください。
パーミッションの削除
パーミッションを削除する機能を追加します。引き続き、PermissionController.php で作業をします。
- あらかじめ作成していた PermissionController.php の destroy関数を、以下のように実装する
public function destroy($name) { $per = base64_decode($name); $permissions = RoleController::getPermissionList(); if (!in_array($per, $permissions)) { return Redirect::back()->withErrors(['delete_permission' => trans('sentinel.invalid_permission')]); } // 削除 foreach (Sentinel::getRoleRepository()->all() as $role) { $role->removePermission($per)->save(); } return Redirect::back()->with(['info' => trans('sentinel.permission_delete_done').":".$per]); }
以上で削除が動作するようになります。先ほど追加した「test」などのパーミッションを、削除欄から選択して消してみてください。
ロールの追加
ロールを追加する処理をRoleController.phpに追加します。
- app/Http/Controllers/Sentinel/RoleController.php をエディターで開く
- Redirectを使えるように、ファイルの最初の方に以下のuse文を追加
use Redirect;
- あらかじめ作成されている store 関数を以下のように実装する
public function store(Request $request) { $this->validate($request, [ 'new_role' => 'required|max:255', 'new_slug' => 'required|max:255', ]); // パーミッションリストの作成 $permissions = []; $pers = self::getPermissionList(); foreach($pers as $per) { $permissions[$per] = $request['new_per_'.str_replace(".", "-", $per)] == "on"; } $role = Sentinel::getRoleRepository()->createModel()->create([ 'name' => $request->new_role, 'slug' => $request->new_slug, 'permissions' => $permissions ]); return Redirect::back()->with(['info' => trans('sentinel.role_create_done')]); }
以上でロールが追加できるようになります。「testroll」などの名前で登録してみてください。
ロールの変更
ロールの変更処理を実装します。
- app/Http/Controllers/Sentinel/RoleController.php をエディターで開く
- あらかじめ作成されている update 関数を以下のように実装する
/** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { $idx = 'role_'.$id.'_'; // バリデーションを実施 $this->validate($request, [ $idx.'name' => 'required|max:255', $idx.'slug' => 'required|max:255', ]); // 修正をチェック $updates = []; $updates[] = trans('sentinel.role_update_done'); // 現ロールを取得 $role = Sentinel::findRoleById($id); if ($role == null) { return Redirect::back()->withInput()->withErrors(['invalid_role' => trans('sentinel.invalid_role')]); } // 名前チェック if ($role->name !== $request[$idx."name"]) { $updates[] = "ロール名: ".$role->name." > ".$request[$idx."name"]; $role->name = $request[$idx."name"]; } // slugチェック if ($role->slug !== $request[$idx."slug"]) { $updates[] = "Slug: ".$role->slug." > ".$request[$idx."slug"]; $role->slug = $request[$idx."slug"]; } // パーミッションの設定 $permissions = self::getPermissionList(); foreach($permissions as $per) { if ($role->hasAccess($per) && (!$request[$idx."per_".str_replace(".", "-", $per)])) { $updates[] = $per." > off"; $role->updatePermission($per, false); } else if (!$role->hasAccess($per) && ($request[$idx."per_".str_replace(".","-",$per)])) { $updates[] = $per." > on"; $role->updatePermission($per, true); } } // 更新 if (count($updates) > 1) { $role->save(); return Redirect::back()->with(['info' => $updates]); } // 更新なし return Redirect::back()->with(['info' => trans('sentinel.no_changed')]); }
info.blade.phpでエラーが発生したら、以下のように修正する
@if (session('info') || isset($info)) <div class="alert alert-info"> @if (session('info')) @if (is_array(session('info'))) <ul> @foreach(session('info') as $ln) <li>{{$ln}}</li> @endforeach </ul> @else {{ session('info') }} @endif @endif @if (isset($info)) {{ $info }} @endif </div> @endif
以上で完了です。登録した「test」ロールの名前やslug、パーミッションを変更して、「変更」を押して、正しく情報が更新されることを確認してください。
ロールの削除
最後に、ロールを削除する処理を実装します。
- app/Http/Controller/Sentinel/RoleController.php をエディターで開く
- あらかじめ作成されている destroy 関数を以下のように実装する
public function destroy($id) { // 削除実行 $role = Sentinel::findRoleById($id); if ($role === null) { return Redirect::back()->withInput()->withErrors(['role-'.$id.'_delete', trans('sentinel.invalid_role')]); } $name = $role->name; // 削除実行 $role->delete(); return Redirect::back()->with(['info' => trans('sentinel.role_delete_done')." : ".$name]); }
以上で完了です。「test」ロールを削除してみてください。
パーミッションの確認
機能が実装できたので、各操作がパーミッションを持つユーザーのみ操作できるように、コントローラーのコンストラクターにコードを追加します。
/** * コンストラクター * 処理に権限チェックのミドルウェアを設定 */ public function __construct() { $this->middleware('permission:role.view', [ 'only' => [ 'index' ] ]); $this->middleware('permission:role.update', [ 'only' => [ 'update' ] ]); $this->middleware('permission:role.create', [ 'only' => [ 'store' ] ]); $this->middleware('permission:role.delete', [ 'only' => [ 'destroy' ] ]); }
/** * コンストラクター * 処理に権限チェックのミドルウェアを設定。パーミッションの権限はロールに準じる */ public function __construct() { $this->middleware('permission:role.create', [ 'only' => [ 'store' ] ]); $this->middleware('permission:role.delete', [ 'only' => [ 'destroy' ] ]); }
これで、ロールやパーミッションの各操作は、以下のパーミッションが有効なロールを持ったユーザーのみが実行できるようになります。
- ロールの操作
- ロールリストの閲覧 role.view
- ロールの追加 role.create
- ロールの編集 role.update
- ロールの削除 role.delete
- パーミッションの操作
できたら、権限のないユーザーで http://0.0.0.0:8080/roles にアクセスしてみましょう。権限がないので、しばらく元の画面に戻ろうとしたのちにエラーが表示されます。通常はrolesに直接アクセスすることはないので、遷移元のページに戻ります。