背景
Laravelは基本的な認可の仕組みを有するフレームワークですが、
基本的なCRUDのポリシー設定(認可)はある程度共通化できるんでないか、と思った。
環境
- PHP 7.2.13
- laravel 5.5
前提
前提として基本的な情報管理サービスでは今回の手法でわざわざPolicyを使い回す必要はないと思います。
なぜなら認可のパターンが限られているから。
よくあるのが以下のようなロールを用意することかと
管理者
なんでもできる(常識の範囲で)
編集権限
編集、削除はできる
サービスのコア情報を消したり、
ログインユーザー情報を変更したりといった一部権限はない
閲覧権限
閲覧のみできる。何も変更できない
といった感じで、
例えば機能ごとに、
「ジャンル情報は削除までできるけど、商品情報は編集できないロール用意しなきゃ」
といった細かいパターンは多分いらない
(あっても使わないし、しんどい)
なくはない
ただ、そんなパターンもなくはないし、クライアントの意向で用意する必要がある時、
あると思います。(そもそもロール自体をエンドユーザーに管理させることも)
そんな時、こうしたよ、って言う一つのやり方です。
手法
抽象クラスを用意して、継承していけばよかろう
その抽象クラス(スーパークラス)は以下のようになります。
(php artisan make:policy
これで作ったポリシークラスがベースです)
スーパークラス
<?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内部でうまいこと返却してやってください
サブクラス
<?php
namespace App\Policies;
/**
* Class GenrePolicy
* 認可
*/
class GenrePolicy extends Policy
{
}
若干無理やりだが、スーパクラスのconstructでサブクラス名から機能idを取得しているので
こうやってサブクラスを抽象クラスを継承しておくだけでおk。
なお、エンティティ特有のアクションがあり、それも認可させたい時などは追加していけば良い
<?php
namespace App\Policies;
/**
* Class GenrePolicy
* 認可
*/
class GenrePolicy extends Policy
{
// エンティティ特有のアクションで認可したい時などは追加する
public function someAction(Admin $admin)
{
$permission = $this->getPermission($admin);
return $permission >= self::DELETABLE;
}
}
補足:Laravelでのポリシーの登録し方
備忘録ではあるのですが、忘れがちなので
モデルとポリシーを紐付ける
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);
}