Laravelで認可(Gate)

# 目次
Laravelの記事一覧は下記
[PHPフレームワークLaravelの使い方](https://qiita.com/toontoon/items/c4d0371e504c37f6576e)

# Laravelバージョン
動作確認はLaravel Framework 7.19.1で行っています

# 認可
簡単に言うと
認証はアクセスしてきているユーザーが誰なのかを認識するもの
認可はアクセスしてきたユーザーに操作を許可するかどうかを判定するもの
です

# Gateを使った認可
簡単に言うと
真偽値を返す関数(Gate)を作っておき、それを簡単に呼び出せるようにしておくようなものです

# 前提条件
[eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)](https://qiita.com/toontoon/items/76fa7f8845b20776077c)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

[Laravelの認証(web画面)](https://qiita.com/toontoon/items/34c871da8660e7d7c075)
本記事は上記記事でLaravelの認証が実装されている前提で書かれています

# 認可に使うカラム追加
(1) マイグレーション作成
コマンドラインで 
`cd sample`
`php artisan make:migration add_rank_to_users --table=users`
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
/sample/database/migrations/xxxx_xx_xx_xxxxxx_add_rank_to_users.phpが現れます

xxxx_xx_xx_xxxxxx_add_rank_to_users.php修正

```xxxx_xx_xx_xxxxxx_add_rank_to_users.php<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddRankToUsers extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->integer('rank')->default(1);
        });
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('rank');
        });
    }
}
```
usersテーブルにrankカラムを追加するマイグレーションです
今回はrankというカラムをつくり、その値によってGateを使った認可が行われるサンプルを作っていきます

(2) マイグレーション実行
コマンドラインで
`php artisan migrate`
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
usersテーブルにrankカラムが追加されます

(3) Model修正
/sample/app/Models/User.php修正

```User.php
‥‥
    protected $fillable = [
        'name', 'email', 'password', 'rank',
    ];
‥‥
```
$fillableプロパティにrank追加

# Gate作成
/sample/app/Gatesフォルダ作成
/sample/app/Gates/UserGate.php作成

```UserGate.php
<?php
namespace App\Gates;

use App\Models\User;

class UserGate
{

    public function __construct()
    {
    }

    public function rank(User $user, int $rank){
        if ($user->rank >= $rank) {
            return true;
        }
        return false;
    }

}
```
rankというメソッドをつくりました
Gateのメソッドの第一引数は必ず、ユーザーインスタンスになります
第二引数以降を定義するかどうかは任意です
第二引数が無くてもいいですし、第三引数、第四引数が定義されていてもいいです

# Gateの登録
/sample/app/Providers/AuthServiceProvider.php修正

```AuthServiceProvider.php
‥‥
    public function boot()
    {
        $this->registerPolicies();
        Gate::define('UserGate-Rank', 'App\Gates\UserGate@rank');
    }
‥‥
```
Gate::define('UserGate-Rank', 'App\Gates\UserGate@rank');追記しました
これでLaravelアプリケーションの中でGateを簡単に呼び出せるようになります

# Controller修正
(1) use文追記
`use Illuminate\Support\Facades\Gate;`
`use App\Models\User;`

(2) /sample/app/Http/Controllers/SampleController.phpにgate1メソッド、gate2メソッドを追記

```
    public function gate1()
    {
        return view('sample.gate1');
    }

    public function gate2(Request $request)
    {
        $data = [];

        $rank = $request->input('rank');

        if (Gate::denies('UserGate-Rank', [$rank])) {
            return redirect('sample/gate1')->withInput();
        }

        $id1user = User::find(1);
        if(!is_null($id1user) && Gate::forUser($id1user)->allows('UserGate-Rank', [$rank])){
            $data['user1_allows'] = 'user1のランクは' . $rank . '以上';
        } else {
            $data['user1_allows'] = 'user1のランクは' . $rank . '以下';
        }

        return view('sample.gate2', $data);

    }
```
・AuthServiceProviderで登録したGateを使うには
Gate::allowsや、Gate::deniesで呼べます
第一引数にAuthServiceProviderのbootメソッドで実行したGate::defineの第一引数を与えます。すると、Gate::defineの第二引数に渡したクラス@メソッドが実行されます
Gate::allowsや、Gate::deniesに渡した第二引数の配列は展開されGateの第二引数、第三引数……となります
・Gate::allowsはGateが返す真偽値がそのまま戻り値になります。Gate::deniesはGateが返す真偽値が逆になります
・Gate::allowsはログインしていないユーザーの場合falseを返します。Gate::deniesはログインしていないユーザーの場合trueを返します。Gateの処理は関係なくなります。
もし、ログインしていないユーザーでもGateを実行したい場合は下記のようにGateに定義してメソッドの第一引数のタイプヒントにクエスチョンマークを付けてください
`public function rank(?User $user, int $rank){`
・Gate::allows、Gate::deniesの場合、Gateの第一引数はAuth::user()になります
・Gate::forUser()->allows、Gate::forUser()->deniesの場合、Gateの第一引数はGate::forUserに与えた引数になります

実際にGateを使う場合は
MiddlewareでGate::deniesを呼び出し、結果によってredirectしたり、
Ruleクラスの中で呼び出すことが多いと思います


(2) /sample/routes/web.phpに下記を追記
`Route::get('sample/gate1', 'SampleController@gate1');`
`Route::get('sample/gate2', 'SampleController@gate2');`

# viewの作成
(1) /sample/resources/views/sample/gate1.blade.phpファイル作成

```gate1.blade.php
<html>
    <head>
        <title>sample</title>
    </head>
    <body>

        <form action="{{ url('sample/gate2') }}" method="get">
            <div>rank<input type="text" name="rank" value="{{ old('rank') }}"></div>
            <input type="submit" >
        </form>

    </body>
</html>
```

(2) /sample/resources/views/sample/gate2.blade.phpファイル作成

```gate2.blade.php
<html>
    <head>
        <title>sample</title>
    </head>
    <body>

        <div>{{$user1_allows}}</div>

        <div>
        @can('UserGate-Rank', [5])
            ログインユーザーのランクは5以上
        @elsecan('UserGate-Rank', [4])
            ログインユーザーのランクは4以上
        @else
            ログインユーザーのランクは3以下
        @endcan
        </div>

        <div>
        @cannot('UserGate-Rank', [2])
            ログインユーザーのランクは2未満
        @elsecannot('UserGate-Rank', [3])
            ログインユーザーのランクは3未満
        @else
            ログインユーザーのランクは3以上
        @endcannot
        </div>

    </body>
</html>
```

blade内でGateを使うにはcanやcannotで呼び出せます
`@can`はGate::allowsと同じです
`@cannot`はGate::deniesと同じです
`@can`や`@cannot`を使うことで、更新ボタンを特定のユーザーにだけ表示させたり、または、逆に非表示にしてりできます

# 動作確認
usersテーブルのrankカラムの値を適当な値にupdateします

```
update users set rank = 4;
```

ログインします
`http://localhost/laravelSample/`
右上のLOGINからログインします

`http://localhost/laravelSample/sample/gate1`
rankを4にupdateしてので
5以上の値を入力して送信ボタンをクリックするとgate1にリダイレクトされ先に進めないと思います
4以下の値を入力して送信ボタンをクリックするとgate2に遷移できます
今回はサンプルのため入力値のエラーチェックを一切行っていないので、gate1の入力欄には数値を入れてください。数値でない場合、制御されていない型エラーになります
実際にシステムを開発するときはちゃんとエラーチェックを入れましょう
エラーチェックの実装の仕方は下記に書いてあります
[Laravelで入力値エラーチェック(validate)を実装する](https://qiita.com/toontoon/items/abf5c52eb0c3fe11003c)