LoginSignup
3
5

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-19

背景

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

環境

  • PHP 7.2.13
  • laravel 5.5

前提

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

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

管理者

なんでもできる(常識の範囲で)

編集権限

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

閲覧権限

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


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

なくはない

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

手法

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

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

スーパークラス

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内部でうまいこと返却してやってください

サブクラス

GenreServiceTest.php
<?php

namespace App\Policies;

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

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

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

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でのポリシーの登録し方

備忘録ではあるのですが、忘れがちなので

モデルとポリシーを紐付ける

AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Genre::class  => GenrePolicy::class,
    ];

コントローラにてアクションとポリシーを紐付ける

/**
 * ジャンルを扱うコントローラー
 */
class GenresController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Genre::class, Genre::class);
    }
3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5