メインコンテンツへスキップ
パッケージが提供するモデルに認可を追加しつつ、利用アプリケーションには独自のルールへ差し替える余地を残したいことがあります。モデルとPolicyの対応を明示し、ユーザーの判定は認可を実行する時点で行う構造にすると、登録処理とリクエストごとの判断を分離できます。 このページは、Laravel公式ドキュメントのデフォルトブランチ 13.x と、2026年11月10日に確認したFrameworkの最新リリース v13.42.0 の公式実装・テストを参照しています。認可の基本は認可ガイド、プロバイダーの読み込みはパッケージ自動検出を参照してください。

利用アプリのUserクラスに依存しない

例として、パッケージの Acme\Reports\Models\Report に update の認可を追加します。acme_reports テーブルには、所有者の認証識別子を保存する owner_id カラムがある前提です。マイグレーションの提供方法はパッケージのマイグレーションで扱っています。 src/Models/Report.php は通常のEloquentモデルです。
パッケージ側のPolicyが App\Models\User を型指定すると、利用アプリの名前空間やモデル構成に依存します。必要なのがユーザーの認証識別子だけなら、Laravelの Authenticatable 契約を使います。 src/Policies/ReportPolicy.php に次を置きます。
識別子の null を先に除外し、DBから取得した整数と文字列の型差は文字列化して比較します。この例では所有者だけに更新を許可します。複数のユーザー種別やGuardを扱うアプリでは、識別子だけで主体を一意に特定できるかも確認し、必要ならユーザー種別やテナントの境界を含む独自Policyへ差し替えます。
Authenticatable は認証識別子などの契約であり、isAdministrator() やロール取得メソッドは提供しません。パッケージ独自の権限情報が必要なら、別の契約を定義して利用アプリから実装を注入する設計にします。

サービスプロバイダーで対応を登録する

src/ReportsServiceProvider.php の boot() で、パッケージが所有するモデルだけを登録します。
Gate::policy() は、モデルのクラス名をキーにPolicyのクラス名を保存します。この登録自体ではPolicyを生成せず、DB照会もユーザー判定も行いません。Gate::getPolicyFor() などでPolicyが必要になったとき、コンテナの make() で生成します。そのため、Policyのコンストラクターには依存サービスを注入できます。 Report と ReportPolicy の配置は標準の自動検出にも合いますが、ここではパッケージが提供する対応を明確にするため明示登録しています。登録だけではHTTPエンドポイントに認可は適用されません。更新処理の前に、コントローラーなどで次を呼び出します。
拒否時には AuthorizationException が発生し、通常のHTTP処理では403レスポンスになります。モデルを渡す update と、モデルのクラス名を渡す create などではPolicyメソッドの引数が異なるため、認可ガイドの呼び出し方に合わせます。

Policy解決の優先順位を読む

Gate::getPolicyFor() は、対象がオブジェクトならクラス名へ変換し、次の順で探します。これは Policyを選ぶ順序 であり、認可全体のフック実行順ではありません。 したがって、同じ Report クラスへの明示登録がある状態では、#[UsePolicy] や命名規則を変えるだけでは差し替わりません。一方、利用アプリが Report を継承したモデルを使う場合、子クラス自身の属性や推測で見つかるPolicyは、親への明示登録より先です。実際に利用するモデルのクラスでも確認します。
guessPolicyNamesUsing() は、追加コールバックのリストではなく、Gateが持つ1つの推測コールバックを置き換えます。独立したパッケージが自分のモデルのためだけに設定すると、利用アプリや他パッケージの推測にも影響します。パッケージが所有するモデルへの policy() 登録を基本にしてください。

利用アプリで明示的に上書きする

同じモデルへ再び policy() を呼ぶと、対応は後の登録で置き換わります。単に別プロバイダーの boot() に置くだけでは読み込み順に依存するので、利用アプリの AppServiceProvider::boot() からアプリの起動完了コールバックを登録する方法があります。
ApplicationReportPolicy は利用アプリが実装するPolicyです。パッケージ側が通常の boot() で登録する構成なら、このコールバックはその後に実行されます。別の起動完了コールバックによる再登録や、後から読み込まれるプロバイダーまで含めて無条件に最優先になる仕組みではありません。パッケージの登録プロバイダーは、この例では遅延プロバイダーにしません。 別の方法として、対応表を変えずに、コンテナで ReportPolicy::class の解決先を差し替えることもできます。resolvePolicy() は毎回コンテナへ解決を依頼しますが、実際に新しいインスタンスになるかは、そのPolicyのバインディング次第です。ユーザーやリクエストの情報をコンストラクターで取得し、singletonのPolicyへ保持する設計は避けます。

認可全体に影響するフックを分ける

通常のGateチェックでは、グローバルな Gate::before() が先に実行されます。そこで null 以外が返ると、Policyによる判定は実行されません。 この図はPolicyが見つかり、対応するメソッドを呼び出せる経路を抜粋しています。その後のグローバルな after フックや、Policyの対象メソッドがない場合のGate定義へのフォールバックは省略しています。 パッケージ内の管理者扱いを実装するために、すべての能力を許可するグローバルな Gate::before() を登録しないでください。他のパッケージや利用アプリの認可まで許可してしまいます。Policy内の before() ならそのPolicyの範囲ですが、対応する能力のメソッドが存在しない場合には呼ばれません。続くメソッドに判断を任せる場合は false ではなく null を返します。 また、この例の update() はnullableではない Authenticatable を要求するため、ゲストではPolicyメソッドを実行しません。グローバルフックなどによる結果の上書きがなければ拒否されます。公開閲覧などでゲストを許容する設計は、nullableなユーザー引数と明示的な判定を別途用意します。

登録と判定を別々にテストする

パッケージテストで紹介しているTestbenchの基底クラスを使い、getPackageProviders() で ReportsServiceProvider::class を読み込む前提です。次は tests/Feature/ReportPolicyTest.php の例です。ユーザーにはLaravelの汎用認証モデルを使い、DBに保存せず判定できます。
forUser() は共有Gateのユーザーを書き換えるのではなく、その時点の対応表・能力・コールバックを渡して別のGateを生成します。したがって、生成済みのユーザー別Gateには、その後の共有GateへのPolicy登録変更が反映されません。起動時に登録を済ませ、ユーザー別Gateを長寿命サービスへ保存しないようにします。 利用アプリ側では、実際のGuard・ユーザーモデルでの判定と、更新エンドポイントに対するHTTPテストも必要です。上のテストだけでは、ルートに認可が適用されていることや、DBの所有者識別子の保存形式までは検証できません。

更新時に維持する契約

  • 所有者・所有者以外・ゲスト・所有者未設定の結果が変わっていないか。
  • 公開している能力名とPolicyメソッドの引数が一致しているか。
  • 利用アプリによるPolicyの差し替えが引き続き有効か。
  • 子モデルをサポートする場合、子クラスで実際に選択されるPolicyが正しいか。
  • アプリのグローバルフックも含めて、実際の更新リクエストが期待どおり許可・拒否されるか。
既存の能力を削除したり、既定の許可・拒否を変えたりする更新は利用アプリの振る舞いに影響します。バージョン互換性管理と合わせ、認可の変更も公開契約として扱います。

参照した一次情報

最終更新日 2026年11月10日