LaravelでPolicy(めっちゃ細かい場合)を使いまわす

# 背景
Laravelは基本的な認可の仕組みを有するフレームワークですが、
基本的なCRUDのポリシー設定（認可）はある程度共通化できるんでないか、と思った。

# 環境
- PHP 7.2.13
- laravel 5.5


# 前提
前提として基本的な情報管理サービスでは今回の手法でわざわざPolicyを使い回す必要はないと思います。
なぜなら認可のパターンが限られているから。

よくあるのが以下のようなロールを用意することかと

#### 管理者
なんでもできる（常識の範囲で）

#### 編集権限
編集、削除はできる
サービスのコア情報を消したり、
ログインユーザー情報を変更したりといった一部権限はない

#### 閲覧権限
閲覧のみできる。何も変更できない

---

といった感じで、
例えば機能ごとに、
「ジャンル情報は削除までできるけど、商品情報は編集できないロール用意しなきゃ」
といった細かいパターンは多分いらない
（あっても使わないし、しんどい）


# なくはない

ただ、そんなパターンもなくはないし、クライアントの意向で用意する必要がある時、
あると思います。（そもそもロール自体をエンドユーザーに管理させることも）
そんな時、こうしたよ、って言う一つのやり方です。


# 手法
抽象クラスを用意して、継承していけばよかろう

その抽象クラス（スーパークラス）は以下のようになります。
（`php artisan make:policy`これで作ったポリシークラスがベースです）

#### スーパークラス
```php5:Policy.php
<?php

namespace App\Policies;

use App\Models\Admin;
use Illuminate\Auth\Access\HandlesAuthorization;

/**
 * Class Policy
 * 認可の基底クラス
 */
abstract class Policy
{
    use HandlesAuthorization;

    /**
     * 閲覧可能レベル
     */
    const VIEWABLE = 1;

    /**
     * 編集可能レベル
     */
    const EDITABLE = 2;

    /**
     * 削除可能レベル
     */
    const DELETABLE = 3;

    /**
     * @var 機能を示したID
     * 例えば、企業情報は"1" ジャンルは"2"といった具合
     */
    protected $feature_id;

    public function __construct()
    {
        // どの機能のポリシーかを、もうクラス名から判断しちゃう（良し悪しあり）
        $entity_name = strtoupper(str_replace('Policy', '', class_basename($this)));
        $this->feature_id = config('const.FEATURE_ID.' . $entity_name);
    }

    /**
     * Determine whether the user can view the Phrase.
     *
     * @param \App\Models\Admin $admin
     * @return mixed
     */
    public function view(Admin $admin)
    {
        $permission = $this->getPermission($admin);

        return $permission >= self::VIEWABLE;
    }

    /**
     * Determine whether the user can create Phrases.
     *
     * @param \App\Models\Admin $admin
     * @return mixed
     */
    public function create(Admin $admin)
    {
        $permission = $this->getPermission($admin);

        return $permission >= self::EDITABLE;
    }

    /**
     * Determine whether the user can update the Phrase.
     *
     * @param \App\Models\Admin $admin
     * @return mixed
     */
    public function update(Admin $admin)
    {
        $permission = $this->getPermission($admin);

        return $permission >= self::EDITABLE;
    }

    /**
     * Determine whether the user can delete the Phrase.
     *
     * @param \App\Models\Admin $admin
     * @return mixed
     */
    public function delete(Admin $admin)
    {
        $permission = $this->getPermission($admin);

        return $permission >= self::DELETABLE;
    }

    /**
     * ユーザー情報からパーミッションを返却
     *
     * @param \App\Models\Admin $admin
     * @return mixed
     */
    private function getPermission(Admin $admin)
    {
        // ここで$admin(管理者)からロールとfeature_id（該当機能）でpermissionをひっぱてくる
        // 設計によるのですが、例えばこんな感じで
        // return $admin->roles->abilities()->featureID($this->feature_id)->permission;
        return $permission;
    }
}

```

こんな感じで機能が変われど使えるように抽象的に作っておくと
例えばGenreに関してのポリシーが作りたければ以下のように使える

コメントにも書いていますが、
管理ユーザー（今回で言うAdmin）からロールおよび機能毎のパーミッションレベルについては
それぞれの設計に合わせてgetPermission内部でうまいこと返却してやってください


### サブクラス

```php5:GenreServiceTest.php
<?php

namespace App\Policies;

/**
 * Class GenrePolicy
 * 認可
 */
class GenrePolicy extends Policy
{
}
```

若干無理やりだが、スーパクラスのconstructでサブクラス名から機能idを取得しているので
こうやってサブクラスを抽象クラスを継承しておくだけでおk。


なお、エンティティ特有のアクションがあり、それも認可させたい時などは追加していけば良い


```php5:GenreServiceTest.php
<?php

namespace App\Policies;

/**
 * Class GenrePolicy
 * 認可
 */
class GenrePolicy extends Policy
{
    // エンティティ特有のアクションで認可したい時などは追加する
    public function someAction(Admin $admin)
    {
        $permission = $this->getPermission($admin);

        return $permission >= self::DELETABLE;
    }
}
```

# 補足：Laravelでのポリシーの登録し方
備忘録ではあるのですが、忘れがちなので

#### モデルとポリシーを紐付ける
```php5:AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Genre::class  => GenrePolicy::class,
    ];
```

#### コントローラにてアクションとポリシーを紐付ける
```php5
/**
 * ジャンルを扱うコントローラー
 */
class GenresController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Genre::class, Genre::class);
    }
```