# laravel 使用者角色權限 功能實測 @ver 0.1.2 @date 2019-04-01 10:27:38 (星期一) @ver 0.1.1 @date 2019-03-16 20:04:16 (星期六) [Associate users with permissions and roles 套件](https://github.com/spatie/laravel-permission) > role : 角色 > > permission : 權限 > > Associate users with roles and permissions : 整合使用者的角色及權限 999. ## 開始 ### 安裝 > 繼續上一個專案 testAuth #### 命令及說明 ```shell # 進入專案 cd ~/jobs/testAuth # 安裝 spatie/laravel-permission composer require spatie/laravel-permission # 等待約 60 秒, 出現命令列提示符號, 查看剛剛加入的套件 ls -l vendor/spatie/ ``` #### 執行結果 ```shell [alex@nvda ~/jobs/testAuth ]$ composer require spatie/laravel-permission Using version ^2.36 for spatie/laravel-permission ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 1 install, 0 updates, 0 removals - Installing spatie/laravel-permission (2.36.1): Downloading (100%) Writing lock file Generating optimized autoload files > Illuminate\Foundation\ComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: beyondcode/laravel-dump-server Discovered Package: fideloper/proxy Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Discovered Package: spatie/laravel-permission Package manifest generated successfully. [alex@nvda ~/jobs/testAuth ]$ ls -l vendor/spatie/ # drwxr-xr-x 10 alexkuo staff 340 3 17 10:17 laravel-permission ``` ### providers 引用 Spatie 打開 `config/app.php` , 先搜尋 `Spatie\Permission\PermissionServiceProvider::class,` 此段程式碼, 是否已被自動加入到 `providers` 的陣列裡, 若沒有則自行加入, 置於此陣列的最後一行即可 #### 命令 ```bash vi config/app.php ``` #### 執行結果 ```shell 'providers' => [ ... Spatie\Permission\PermissionServiceProvider::class, ], ``` ### Spatie\Permission 的 migration 用下列命令生成 migration 用的檔案 #### 命令 ```bash php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations" ``` #### 執行結果 ```shell [alex@nvda ~/jobs/testAuth ]$ php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations" Copied File [/vendor/spatie/laravel-permission/database/migrations/create_permission_tables.php.stub] To [/database/migrations/2019_03_18_105704_create_permission_tables.php] Publishing complete. [alex@nvda ~/jobs/testAuth ]$ ``` 使用 `git st` , 我們發現增加了一個 migration 檔案, 名為 `database/migrations/2019_03_18_105704_create_permission_tables.php` , `public function up()` 裡面有 5 個 tables , 整理如下所示 #### 資料表清單 ```shell $tableNames['role_has_permissions']); $tableNames['model_has_roles']); $tableNames['model_has_permissions']); $tableNames['roles']); $tableNames['permissions']); ``` ### migrate #### 命令 ```bash php artisan migrate --seed php artisan migrate:fresh --seed # 注意 : 所有資料庫會全部還原重設 ``` 如果成功, 資料庫會增加上述的 5 個 tables #### 執行結果 ```shell # 進入 mysql mysql> show tables; +-------------------------+ | Tables_in_alex_testauth | +-------------------------+ | migrations | | model_has_permissions | | model_has_roles | | password_resets | | permissions | | role_has_permissions | | roles | | users | +-------------------------+ 8 rows in set (0.00 sec) ``` ### Spatie\Permission 的 config 使用以下命令生成 config 用的檔案 #### 命令 ```bash php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config" ``` #### 執行結果 ```shell [alex@nvda ~/jobs/testAuth ]$ php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config" Copied File [/vendor/spatie/laravel-permission/config/permission.php] To [/config/permission.php] Publishing complete. [alex@nvda ~/jobs/testAuth ]$ ``` 用 `git st` , 又多了一個設定檔, 檔名為 `config/permission.php` , 這個檔設定對應使用 `table_name, cache` 等相關訊息, 現在先直接用這個檔的預設值, 暫不修改. ### use Spatie\Permission 套件 因為 `Spatie\Permission` 是跟 `User Model` 綁在一起, 要使用此套件提供的各項功能, 先在 `app/User.php` 加入 `use Spatie\Permission\Traits\HasRoles;` 及 class 裡的 `use HasRoles;` 讓後續程式可以直接取用. #### 命令 ```bash vi app/User.php ``` #### app/User.php 程式碼 ```php forgetCachedPermissions(); // 建立權限方法一, 各類角色使用的權限 Permission::create(['name' => '新增留言']); Permission::create(['name' => '刪除自己留言']); Permission::create(['name' => '刪除他人留言']); Permission::create(['name' => '救回所有留言']); // 建立權限方法一, 單獨存在的權限 Permission::create(['name' => '特異功能']); // // 建立權限方法二, 使用陣列建立權限. (先用方法一來實驗) // $permissions = [ // '新增留言', // '刪除自己留言', // '刪除他人留言', // '救回所有留言', // '特異功能', // ]; // foreach ($permissions as $permission) { // Permission::create(['name' => $permission]); // } // 建立角色, 並賦于權限 $role1 = Role::create(['name' => '討論管理者']); $role1->givePermissionTo('新增留言'); $role1->givePermissionTo('刪除自己留言'); $role1->givePermissionTo('刪除他人留言'); $role1->givePermissionTo('救回所有留言'); $role2 = Role::create(['name' => '一般會員']); $role2->givePermissionTo('新增留言'); $role2->givePermissionTo('刪除自己留言'); // 用 where 條件取出角色, 給他們對應的角色或權限, 數量不限 $user = App\User::where('email', 'a@b.com')->first(); $user->assignRole('討論管理者'); $user->givePermissionTo('特異功能'); $user = App\User::where('email', 'b@c.com')->first(); $user->assignRole('一般會員'); } } ``` `DatabaseSeeder.php` 也別忘了要加 `PermissionsSeeder::class` 到 `call([...])` #### 命令 ```bash vi database/seeds/DatabaseSeeder.php ``` #### 執行結果 ```php public function run() { $this->call([ UsersTableSeeder::class, PermissionsSeeder::class, ]); } ``` 完成 Seeder 類別的編寫, migrate 前, 必須使用 `dump-autoload` 命令重新產生 Composer 的自動載入程式 #### 命令 ```bash composer dump-autoload ``` 因為是實驗, 讓資料庫一切重新開始, 所以一不做二不休用 `migrate:fresh --seed` 暫時省卻一些繁雜的操作步驟, 真實環境就要注意是否備份資料庫 #### 命令 ```bash php artisan migrate:fresh --seed # 注意 : 備份資料庫 ``` #### 執行結果 ```shell [alex@nvda ~/jobs/testAuth ]$ php artisan migrate:fresh --seed Dropped all tables successfully. Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table Migrating: 2019_03_18_110234_create_permission_tables Migrated: 2019_03_18_110234_create_permission_tables Seeding: UsersTableSeeder Seeding: PermissionsSeeder Database seeding completed successfully. [alex@nvda ~/jobs/testAuth ]$ ``` 如果成功, 進入 mysql 查看 5 個相關 tables 資料表的筆數, 看起來是沒問題. MYDATABASE 請修改成自己的資料庫名稱 #### 命令 ```bash SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema in ('MYDATABASE') ORDER BY table_name; ``` #### 執行結果 ```shell mysql> SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema in ('MYDATABASE') ORDER BY table_name; +-----------------------+------------+ | table_name | table_rows | +-----------------------+------------+ | migrations | 2 | | model_has_permissions | 0 | | model_has_roles | 1 | | password_resets | 0 | | permissions | 5 | | roles | 2 | | role_has_permissions | 6 | | users | 2 | +-----------------------+------------+ 8 rows in set (0.01 sec) mysql> ``` 999. ## 看看權限有沒有, 角色有沒有 ### blade 的應用 我們先拿前端 blade 開刀, 現在動 `welcome.blade.php` 它即可, 只要改一點點, 就是 body 裡 `
` 下, 加一行改為 `@include('permissionsTest')`, 然後在同一個目錄下建立一個新 blade 檔案名為 `permissionsTest.blade.php` #### resources/views/welcome.blade.php 網頁程式碼 ```html ...(前略)
@include('permissionsTest')
Laravel ...(後略) ``` #### resources/views/permissionsTest.blade.php 網頁程式碼 ```html
我是誰? @if (Auth::guest()) 我是訪客 @else 我不是訪客 @endif
@if (Auth::check()) 我的名字是 '{{ Auth::user()->name }}', id 為 '{{ Auth::user()->id }} ' @else 未授權, 請登入會員!!! @endif

角色1 '討論管理者' : @hasrole('討論管理者') 我是 '討論管理者' @else 我不是 '討論管理者' @endhasrole
角色2 '一般會員' : @hasrole('一般會員') 我是 '一般會員' @else 我不是 '一般會員' @endhasrole

權限1 '特異功能' : @can('特異功能') 我可以執行 @else 我不能執行 '特異功能' @endcan
權限2 '救回所有留言' : @can('救回所有留言') 我可以執行 @else 我不能執行 '救回所有留言' @endcan
權限3 '刪除自己留言' : @can('刪除自己留言') 我可以執行 @else 我不能執行 '刪除自己留言' @endcan
``` 執行 `php artisan serve --port=2019` 進入 [瀏覽器](http://127.0.0.1:2019) , 在 `Login` 用兩個不同的帳號登入, 看看顯示出來的結果. 999. ## 主機端角色及權限 MVC 應用 我們現在來想像一個場景, 以方便後續的實作, 在執行『投稿審核』時, 使用者必須登入, 且角色為『教學區管理員』, 這位使用者就能審核所有人的投稿, 因此我們會在他看到的頁面上列出所有尚未被審核通過的投稿清單, 可以連結進入查看審核, 並且有《投稿審核完成》的按鍵. 此外還有一個特別的『投稿不需被審核』獨立權限(特權), 不在任何角色的編制之下, 擁有此特權的使用者可以直接投稿上線. ### 修改 Seeder 程式碼 為了稍候測試時使用, 兩支 Seeder 程式碼都要重新翻修, 我們會多 3 個帳號, 6 個權限, 2 個角色, 並增加使用者對應的權限, 以下為完整 `public function run()` 程式碼, 可以直接複製貼上 #### database/seeds/UsersTableSeeder.php 程式碼 ```php public function run() { // create a demo user DB::table('users')->insert([ 'name' => '測試帳號1', 'email' => 'a@b.com', 'password' => bcrypt('hello'), ]); Factory(App\User::class)->create([ 'name' => '測試帳號2', 'email' => 'b@c.com', // factory default password is 'password' ]); DB::table('users')->insert([ ['name' => '測試帳號3', 'email' => 'c@d.com', 'password' => bcrypt('hello')], ['name' => '測試帳號4', 'email' => 'd@e.com', 'password' => bcrypt('hello')], ['name' => '測試帳號5', 'email' => 'e@f.com', 'password' => bcrypt('hello')], ]); } ``` #### database/seeds/PermissionsSeeder.php 程式碼 ```php public function run() { // 重設角色及權限的快取 Reset cached roles and permissions // 或用 artisan 命令列執行重設 // php artisan cache:forget spatie.permission.cache app()[PermissionRegistrar::class]->forgetCachedPermissions(); // 建立權限方法一, 各類角色使用的權限 Permission::create(['name' => '新增留言']); Permission::create(['name' => '刪除自己留言']); Permission::create(['name' => '刪除他人留言']); Permission::create(['name' => '救回所有留言']); // 建立權限方法一, 單獨存在的權限 Permission::create(['name' => '特異功能']); // 建立權限方法二, 增加兩個投稿權限, 四個電子書權限 $permissions = [ '投稿審核', '投稿不需被審核', '電子書上傳', '電子書刪除', '電子書借閱', '電子書錯誤回報', ]; foreach ($permissions as $permission) { Permission::create(['name' => $permission]); } // 建立角色, 並賦于權限 $role1 = Role::create(['name' => '討論管理者']); $role1->givePermissionTo('新增留言'); $role1->givePermissionTo('刪除自己留言'); $role1->givePermissionTo('刪除他人留言'); $role1->givePermissionTo('救回所有留言'); $role2 = Role::create(['name' => '一般會員']); $role2->givePermissionTo('新增留言'); $role2->givePermissionTo('刪除自己留言'); // 增加第三、四種角色及其所屬權限 $role3 = Role::create(['name' => '教學區管理員']); $role3->givePermissionTo('投稿審核'); $role3->givePermissionTo('新增留言'); $role3->givePermissionTo('刪除自己留言'); $role3->givePermissionTo('刪除他人留言'); $role3->givePermissionTo('救回所有留言'); $role4 = Role::create(['name' => '電子書管理員']); $role4->givePermissionTo('電子書上傳'); $role4->givePermissionTo('電子書刪除'); $role4->givePermissionTo('電子書借閱'); $role4->givePermissionTo('電子書錯誤回報'); // 用 where 條件取出使用者1, 給他們對應的角色或權限, 數量不限 $user = App\User::where('email', 'a@b.com')->first(); $user->assignRole('討論管理者'); $user->assignRole('電子書管理員'); $user->assignRole('一般會員'); $user->givePermissionTo('特異功能'); $user->givePermissionTo('投稿不需被審核'); // 使用者2 $user = App\User::where('email', 'b@c.com')->first(); $user->assignRole('一般會員'); // 使用者3 $user = App\User::where('email', 'c@d.com')->first(); $user->assignRole('教學區管理員'); $user->assignRole('一般會員'); // 使用者4 $user = App\User::where('email', 'd@e.com')->first(); $user->assignRole('一般會員'); $user->givePermissionTo('投稿不需被審核'); // 使用者5 // 沒有任何角色及權限 } ``` > MVC 模式, 通常 `Model` 只管資料庫模型, `View` 做頁面輸出顯示 > > 原則上 `Controller` 負責轉發各項請求, 對請求進行處理, 所以會去呼叫各式各樣的功能類別, 包含程式邏輯...等 > > `Route` 分析輸入或跳轉進來的 `URL` 路徑後, 根據符合條件, 執行對應的 `Controller` 程式, 處理完所有指定之工作, 呼叫 `View` 顯示網頁 ### 增修 Route 程式碼 在 `routes/web.php` 這裡, 有先後順序, 先符合的先執行, 把這段程式碼加在最後. #### routes/web.php 程式碼 ```php ...(前略) // '/測試', 中文 URL 沒問題, 但為了方便在網址列輸入, 你可以改成 '/test' Route::get('/測試', 'HomeController@test'); // 或用英文 Route::get('/test', 'HomeController@test'); ``` 當瀏覽器的 `URL` 路徑是 `/測試` 而且是用 `get` 方式進入. `laravel` 會找到這一行程式, 意思是執行 `HomeController.php` 裡面的 `public function test()` 函數. ### 增加 Controller 程式碼 因為要方便調用 `User Model` 及 `Spatie 的 Permission` 所以別忘了在最上方要加上 4 行 `use`. 此外 `public function test(){...}` 整段程式碼直接複製貼到 `app/Http/Controllers/HomeController.php` 的 `class HomeController extends Controller { ... }` 裡. 原有的程式碼不要破壞掉. #### app/Http/Controllers/HomeController.php 程式碼 ```php ...(前略) use App\User; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; use Spatie\Permission\PermissionRegistrar; class HomeController extends Controller { ...(中略) /** * Route::get('/測試', 'HomeController@test'); * @date 2019-04-01 08:26:22 (星期一) * @return */ public function test() { // $vars 陣列是要傳遞運算處理之後的值給 view() 顯示 $vars = []; if (Auth::check()) { // 會員已登入成功 // Auth 取得登入會員 $user 物件 $user = Auth::user(); // 用 Auth 取得登入會員 ID $userID = Auth::id(); // 取出此 USER 的所有角色 $roles = $user->getRoleNames(); // Returns a collection $vars['rolesCount'] = count($roles); $vars['roles'] = $roles; // 取出此 USER 的所有權限 $permissions = $user->getAllPermissions(); $vars['permissionsCount'] = count($permissions); $vars['permissions'] = $permissions; // 角色判斷 // $role = Role::findByName('教學區管理員'); $vars['角色_教學區管理員'] = null; if ($user->hasRole(Role::findByName('教學區管理員'))) { $vars['角色_教學區管理員'] = '符合條件'; } $vars['角色_討論管理者'] = null; if ($user->hasRole(Role::findByName('討論管理者'))) { $vars['角色_討論管理者'] = '符合條件'; } $vars['角色_一般會員'] = null; if ($user->hasRole(Role::findByName('一般會員'))) { $vars['角色_一般會員'] = '符合條件'; } // 權限判斷 $vars['獨立權限_特異功能'] = null; if ($user->hasPermissionTo('特異功能')) { $vars['獨立權限_特異功能'] = '符合條件'; } $vars['獨立權限_投稿不需被審核'] = null; if ($user->hasPermissionTo('投稿不需被審核')) { $vars['獨立權限_投稿不需被審核'] = '符合條件'; } $vars['權限_投稿審核'] = null; if ($user->hasPermissionTo('投稿審核')) { $vars['權限_投稿審核'] = '符合條件'; } $vars['權限_新增留言'] = null; if ($user->hasPermissionTo('新增留言')) { $vars['權限_新增留言'] = '符合條件'; } } return view('test', $vars); } } ``` 上面這段程式最後一行 `return view('test', $vars);` 表示最終各式各樣運算結果用 `$vars` 傳遞回 `resources/views/test.blade.php` 然後顯示成網頁. > 注意 : 檔案路徑 `resources/views/` 跟副檔名 `.blade.php` 不用寫在 `view(...)` 的第一個參數裡, `laravel` 會自動處理. ### 建立新的 View 程式碼 #### resources/views/test.blade.php 程式碼 ```php
你好, 我的名字是 '{{ Auth::user()->name }}', id 為 '{{ Auth::user()->id }}'
我有 {{ $rolesCount }} 個角色 @foreach($roles as $key => $value)
角色 {{ $key + 1 }} : {{ $value }}
@endforeach
我有 {{ $permissionsCount }} 個權限 @foreach($permissions as $key => $value)
權限 {{ $key + 1 }} : {{ $value['name'] }}
@endforeach
'教學區管理員' 的角色 @if ($角色_教學區管理員) @endif
'討論管理者' 的角色 @if ($角色_討論管理者) @endif
'一般會員' 的角色 @if ($角色_一般會員) @endif

'特異功能' 的權限 @if ($獨立權限_特異功能) @endif
'投稿不需被審核' 的權限 @if ($獨立權限_投稿不需被審核) @endif
'投稿審核' 的權限 @if ($權限_投稿審核) @endif
'新增留言' 的權限 @if ($權限_新增留言) @endif
``` ### git #### 命令 ```bash git st -s ``` #### 執行結果 ```shell [alex@nvda ~/jobs/testAuth ]$ git st -s M app/Http/Controllers/HomeController.php M database/seeds/PermissionsSeeder.php M database/seeds/UsersTableSeeder.php M routes/web.php ?? resources/views/test.blade.php [alex@nvda ~/jobs/testAuth ]$ ``` 從 `git st -s` 這裡可以可以查到, 修改了 4 個現有的檔案 (Controller, Seeder, Route), 並新建立了 1 個 `test.blade.php` #### 命令 ```bash git add . git commit -m '主機端角色及權限應用 修改了 5 個現有的檔案, 並新建立了 1 個 test.blade.php' ``` 好了, 萬事俱備, 只欠東風, 在命令列執行 `php artisan serve --port=2019` 進入 [瀏覽器](http://127.0.0.1:2019/測試) , 用幾個不同帳號 `Login` , 比較每個帳號在網頁上顯示的角色及權限. 這一節學習完成後, 大致可知道 `Model`、`View`、`Controller` 及 `Route` 的簡單流程. 整套系統的運作, 都是在這些地方丟過來, 轉過去. `laravel` 幫我們建立了很多重複繁雜的基礎工具, 節省不少時間, 只要專注在我們的應用邏輯上, 真是太棒了. 999. ## 本章總結 ### 本章使用 laravel 命令總結 ```bash php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations" # 產生 Spatie Permission 的 migrations php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config" # 產生 Spatie Permission 的 config php artisan cache:forget spatie.permission.cache # 清除 Spatie Permission 的 cache php artisan make:seeder PermissionsSeeder # 建立 PermissionsSeeder Class 程式 php artisan migrate --seed # 生成及遷移尚未處理的資料庫, 並產生 seeder php artisan migrate:fresh --seed # 資料庫會全部還原重設, 並產生 seeder ```