Laravel の認可処理を見直していて、$user->can() という書き方が改めて分かりやすいと感じました。
「このユーザーはこの操作をしてよいか?」 という意味が、そのままコードに表れているからです。
特に FormRequest の authorize() と組み合わせると、認可の意図がかなり読みやすくなるので、自分用の整理としてまとめておきます。
例:FormRequest での can() の使い方
例えば、更新リクエストを受け取る FormRequest が次のように書かれているとします。
class RecipientUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('update', $this->route('recipient'));
}
}
これ、かなり自然に読めます。
-
this->user()→ 現在ログインしているユーザー -
can('update', ...)→ このユーザーはこのデータを更新できる?
つまり、
「現在のユーザーに更新権限があるなら、このリクエストを通す」
という意味になります。
can() は authorize() を再帰的に呼んでいるわけではない
最初に少し気になったのがここです。
FormRequest::authorize() の中で can() を呼んでいるので、
ぱっと見では「認可判定の中でまた認可判定している?」ようにも見えます。
でも実際には違います。
$user->can() は FormRequest::authorize() を呼び直すのではなく、
Laravel の Gate を通じて Policy を呼び出すメソッドです。
処理の流れを簡単に書くとこうなります。
FormRequest::authorize()
└─ $this->user()->can('update', $recipient)
└─ Gate::check('update', $recipient)
└─ RecipientPolicy::update(User $user, Recipient $recipient)
└─ 判定結果を返す
つまり authorize() は、
Gate に問い合わせて、その結果をそのまま返しているだけ
という構造です。
それぞれの役割
少し整理しておくと、各要素の役割はこんな感じです。
$this->user()
現在ログインしているユーザーを取得します。
認可の世界では
「誰が操作しようとしているか」
を表します。
$user->can('update', $recipient)
ここが認可チェックの入口です。
can() は
- 何の操作をしたいのか
- どの対象に対して行うのか
を渡して、許可されているかを判定します。
この書き方の良いところは、主語がユーザーになっていることです。
$user->can('view', $document)
$user->can('update', $recipient)
$user->can('delete', $post)
「ユーザーはそれをできるのか?」 という読み方がそのままできます。
Policy が呼ばれる
Recipient モデルに対応する Policy が登録されていれば、
Laravel は自動的にそのメソッドを呼びます。
例えば次のようなものです。
class RecipientPolicy
{
public function update(User $user, Recipient $recipient): bool
{
return $user->corp_id === $recipient->corp_id;
}
}
つまり
can('update', $recipient)
は最終的に
RecipientPolicy::update($user, $recipient)
を呼ぶための入口になります。
authorize() と can() の関係
役割を整理するとこうなります。
| メソッド | 役割 |
|---|---|
FormRequest::authorize() |
このリクエストを通してよいか判定 |
$user->can() / Gate |
認可ロジック本体(Policy を呼ぶ) |
$this->authorize() (Controller) |
認可チェックして NG なら例外 |
ポイントは、
authorize() 自体は認可ロジックの本体ではない
ということです。
authorize() は
「このリクエストを受け付けていいですか?」
という入口のチェック場所です。
コントローラー側の認可と重複することがある
同じ認可をコントローラーで書くこともあります。
public function update(RecipientUpdateRequest $request, Recipient $recipient)
{
$this->authorize('update', $recipient);
// 更新処理
}
これはもちろん正しいです。
ただし、すでに FormRequest 側で認可しているなら
コントローラー側の認可は重複になります。
public function update(RecipientUpdateRequest $request, Recipient $recipient)
{
// 認可済み
}
この形にすると、コントローラーが少しすっきりします。
Request で認可するか、Controller で認可するか
これは好みや設計にもよりますが、自分の整理ではこんな感じです。
FormRequest に書くと良いケース
- リクエスト単位で完結する認可
- バリデーションと同じ入口で弾きたい
- コントローラーをシンプルにしたい
Controller に書くと良いケース
- 処理の途中で認可が必要
- 条件によって認可対象が変わる
- 複数の認可を段階的に行う
can() はかなり読みやすい
今回改めて思ったのは、
$user->can('update', $recipient)
という書き方は、かなり直感的です。
認可ロジックの中身は Policy に書くとして、 呼び出し側では
「ユーザーはこの操作をできる?」
という表現だけ書けばよい。
認可は if 文を書き始めるとすぐ散らかるので、
入口を can() に揃えるのはやはり良い設計だと思いました。
まとめ
今回整理したポイントです。
can()は Laravel の Gate を呼び出す- Gate は 対応する Policy を探して実行する
FormRequest::authorize()は その結果を返す場所authorize()の中でcan()を呼んでも再帰にはならない- Request で認可するなら Controller の認可を省けることもある
Laravel の認可は、最初は少し分かりにくいですが、
「user → can → Gate → Policy」
という流れを覚えると、かなり整理しやすくなります。