サブスクリプションプランの制限をどう設計するか

私はたいてい、ユーザーのサブスクリプションプランを users テーブルに保存します。たとえば、サブスクリプションプランを参照する plan_id です。

まずは土台を作りましょう。

サブスクリプションプラン(plans.php)

return [

	cheap_plan ⇒ [

		‘maximum_list’ ⇒ 20,

	],

	expensive_plan ⇒ [

		‘maximum_list’ ⇒ 50,

	],

];

コントローラーでは次のように使えます(このエンドポイントを使えるのはログイン済みのユーザーだけとします)。

public function create()
{
	$postCount = Auth::user()->loadCount('posts')->posts_count;

	if ($postCount > Auth::user()->plan->maximum_list) {
		abort(403, "You've reached your maximum list. Please upgrade your plan.");
	}

	// proceed to create the list
	// $list = List::create(...)

	// return redirect()->route('lists.show', ['list' => $list]);
}

これは有効な方法で、多くのアプリケーションでは、作成数の上限をチェックする目的を十分に果たすと思います。

ただ、ご指摘のとおり、もう少し「一元化された」方法もあります。

GateとPolicyを使う方法です。

先ほどのコードをGateにリファクタリングしてみましょう。

// AuthServiceProvider.php

class AuthServiceProvider extends ServiceProvider
{
	//

	public function boot()
	{
		$this->registerPolicies();

		Gate::define('create-list', function($user) {
			$postCount = Auth::user()->loadCount('posts')->posts_count;

			if ($postCount > Auth::user()->plan->maximum_list) {
				return Response::deny("You've reached your maximum list. Please upgrade your plan.");
			}

			return Response::allow();
		});
	}
}

コントローラーは次のように書き換えられます。

public function create()
{
	$this->authorize('create-list');
	// or Gate::allows('create-list');
	// or Gate::inspect('create-list'); // if you want more customized response.

	// proceed to create the list
	// $list = List::create(...)

	// return redirect()->route('lists.show', ['list' => $list]);
}

これでもう十分かもしれません。とはいえ、もっと作法に沿った方法はないでしょうか。

そのとおり、Policyを使う方法です。

Policyは、ドメイン内の「リソース」やモデルを扱うときに向いていると言われます。今回の例でいえば、Listモデルです。

一方、Gateは「アクション」を扱うときに向いています。

ただ、私の個人的な意見としては、可能な限りPolicyを使うのがよいと思います。ファイルを分ける口実ができるうえ、AuthServiceProvider が散らかりません(そもそも、本当に必要な場合を除いて、サービスプロバイダーはあまり触りたくありません)。

先ほどのコードをPolicyにリファクタリングしてみましょう。

class ListPolicy
{
	use HandlesAuthorization;

	public function create(User $user)
	{
		$postCount = Auth::user()->loadCount('posts')->posts_count;

		if ($postCount > Auth::user()->plan->maximum_list) {
			return Response::deny("You've reached your maximum list. Please upgrade your plan.");
		}

		return Response::allow();
	}

}

メソッド名がリソースコントローラーに対応しているのにお気づきでしょうか。これはLaravelチームによる意図的な設計だと思います。さすがです。

コントローラーでは、次のようにシンプルに使えます。

public function create()
{
	$this->authorize('create', List::class);
	// or Gate::allows('create-list', List::class);
	// or Gate::inspect('create-list', List::class); // if you want more customized response.

	// proceed to create the list
	// $list = List::create(...)

	// return redirect()->route('lists.show', ['list' => $list]);
}

List::class は、どのPolicyクラスを使うかをLaravelが判断するための目印にすぎません。

結果として、この方法なら、たとえば10個もの別々のコントローラーからリストを作成できてしまう、といった問題を避けられます。ここに $this->authorize() を置くだけで済みます。

あわせて読みたい:WordPressのようなコンテンツ管理システムを自作する方法。また、WooCommerce向けの無料サブスクリプションプラグイン「Sawir Renew」は、WordPressプラグインのページでご覧いただけます。

この記事は参考になりましたか?

新しい記事を公開したときに、このような記事をお届けしたいので、メールアドレスを教えていただけますと幸いです。

    迷惑メールは送りません。配信はいつでも解除できます。