如何設計訂閱方案的使用限制

我通常會把使用者的訂閱方案存放在 users 資料表中,例如用 plan_id 來對應訂閱方案。

我們先來搭個範例環境……

我們的訂閱方案 subscription plans.php

return [

	cheap_plan ⇒ [

		‘maximum_list’ ⇒ 20,

	],

	expensive_plan ⇒ [

		‘maximum_list’ ⇒ 50,

	],

];

接著就可以在 controller 中這樣使用(這裡假設只有已登入的使用者才能呼叫這個 endpoint)

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();
		});
	}
}

接著在 controller 中可以改寫成這樣

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。

一般的說法是,處理領域中的「資源」或 model 時,最適合用 Policy,就像我們這個例子中的 List model。

而 Gate 則比較適合處理「動作」。

不過,我個人的看法是,只要可以,就盡量用 Policy。除了可以順理成章地把程式拆成不同檔案之外,也不會把 AuthServiceProvider 弄得雜亂(順帶一提,除非真的有必要,否則我不太喜歡去改動 Service Provider)。

我們把前面的程式碼重構成 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();
	}

}

有注意到這些方法的命名和 resource controller 很相似嗎?我想這是 Laravel 團隊刻意的設計,值得稱讚。

然後在 controller 中就可以直接這樣使用

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 只是用來幫助 Laravel 推斷要使用哪一個 Policy 類別。

總的來說,這樣做可以避免一個問題:您可能會在 10 個不同的 controller 裡建立清單。我們只要在各處加上 $this->authorize(),就大功告成了。

延伸閱讀:如何打造自己的內容管理系統(像 WordPress 那樣),或是到我們的 WordPress 外掛看看 Sawir Renew,這是一款適用於 WooCommerce 的免費訂閱外掛。

這篇文章對您有幫助嗎?

留下您的電子郵件地址,之後每當有新文章發布,我就會寄給您類似的內容。

    我不會寄送垃圾郵件,您可以隨時取消訂閱。