Как спроектировать ограничения тарифных планов подписки

Обычно я храню тарифный план пользователя в таблице users. Допустим, в поле plan_id, которое ссылается на тарифные планы подписки.

Подготовим почву…

наши тарифные планы.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 нужен лишь для того, чтобы Laravel мог определить, какой класс Policy использовать.

В итоге так можно избежать проблем, когда список буквально можно создать из 10 разных контроллеров. Достаточно поставить туда $this->authorize(), и дело сделано.

Читайте также: Как создать собственную систему управления контентом (как WordPress) или познакомьтесь с Sawir Renew, бесплатным плагином подписок для WooCommerce, в разделе плагины для WordPress.

Узнали из этой статьи что-то новое?

Оставьте, пожалуйста, свой адрес электронной почты, и я буду присылать вам похожие материалы, как только выйдет новый.

    Никакого спама. Отписаться можно в любой момент.