Laravelには認可機能として「ゲート」と「ポリシー」が用意されています。
「ゲート」は下記の記事で試してみたので、今回は「ポリシー」を試してみます。
ポリシーの生成
artisanコマンドでポリシーを生成できます。--modelオプションを使用すればモデルに関連付けたポリシーを生成することができます。
php artisan make:policy ArticlePolicy --model=Article
artisanコマンドを実行するとapp/Policiesディレクトにファイルが生成されます。生成されたファイルにはいくつかのメソッドがあらかじめ作られています。
<?php
namespace App\Policies;
use App\Models\Article;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class ArticlePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function viewAny(User $user)
{
//
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $user
* @param \App\Models\Article $article
* @return mixed
*/
public function view(User $user, Article $article)
{
//
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function create(User $user)
{
//
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\Article $article
* @return mixed
*/
public function update(User $user, Article $article)
{
//
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Article $article
* @return mixed
*/
public function delete(User $user, Article $article)
{
//
}
/**
* Determine whether the user can restore the model.
*
* @param \App\Models\User $user
* @param \App\Models\Article $article
* @return mixed
*/
public function restore(User $user, Article $article)
{
//
}
/**
* Determine whether the user can permanently delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Article $article
* @return mixed
*/
public function forceDelete(User $user, Article $article)
{
//
}
}
ポリシーを使用したアクションの認可
ここから、ポリシーを使用してユーザーにある操作の許可・拒否を実装していきます。例えば記事の更新は、記事を作成したユーザーのみしかできないようにしてみます。
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\Article $article
* @return mixed
*/
public function update(User $user, Article $article, $category, $role)
{
return $user->id === $article->user_id;
}
定義したアクションは、
- ユーザーモデル経由
- コントローラヘルパ経由
- ミドルウェア経由
- Bladeテンプレート経由
で呼び出せます。
ユーザーモデル経由
App\Models\Userモデルにはアクションを認可するための特別なメソッドcan・cannotが含まれていて、これらのメソッドを使用することでアクションを認可することができます。
public function edit(Request $request, Article $article)
{
if ($request->user()->cannot('update', $article)) {
abort(403);
}
return view('articles.edit', ['article' => $article]);
}
コントローラヘルパ経由
コントローラからauthorizeメソッドを呼び出すことによっても、アクションを認可することができます。
public function edit(Request $request, Article $article)
{
$this->authorize('update', $article);
return view('articles.edit', $article);
}
ミドルウェア経由
canミドルウェアを使用することで、アクションの認可ができます。
Route::get('/article/edit/{article}', 'App\Http\Controllers\ArticlesController@edit')
->middleware(['auth'])
->middleware('can:update,article');
Bladeテンプレート経由
bladeテンプレート内で@canまたは@cannotディレクティブを使用することで、アクションを認可できます。
@can('update', $article)
<input type="submit" value="send">
@else
<h3>更新できません</h3>
@endcan
ポリシーフィルター
ポリシーファイルでbeforeメソッドを定義すると、ほかのポリシーメソッドが呼び出される前に実行されます。例えばadminユーザーはすべてのアクションを許可したいとなった場合、
public function before(User $user, $ability)
{
if ($user->role === 'admin') {
return true;
}
}
このように定義するとadminユーザーをすべてのアクションが許可されます。
ポリシーメソッドに追加のパラメータを渡す
例えば、あるカテゴリーの記事に関してはすべてのユーザーが更新できるようにしたいとなった場合、
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\Article $article
* @return mixed
*/
public function update(User $user, Article $article, $category)
{
return $user->id === $article->user_id || $category === 'free';
}
このように、updateメソッドの第3引数に$categoryを追加します。ポリシーを呼び出す側では、
public function edit(Request $request, Article $article)
{
$this->authorize('update', [$article, $request->category]);
return view('articles.edit', $article);
}
このように、最初の引数にアクション名を渡し2番目の引数に配列を渡します。配列の1番目には、どのポリシーを使用するかを指定し、2番目以降にポリシーメソッドに渡すパラメータを指定します。
おまけ
ポリシーを使用するにあたって、認証機能・必要なテーブルデータを用意する必要がある場合は下記の記事を参考に。