Easy to use Laravel Features, Abilities, Usages and Consumptions

v0.1.0 2024-05-06 18:52 UTC

This package is auto-updated.

Last update: 2024-05-12 16:52:25 UTC


README

Table Of Contents

Introduction

While there are many packages in Laravel like Laravel Pennant (for feature flags) and Spatie Permission (for managing user permissions and roles), I prefer making my own method for creating features that can be abilities or consumables.

For example, I want to make an ability feature called "can-post" so users can create posts. Also, I'm thinking of a consumable feature called "storage" for using storage in the app. I plan to have different levels of usage for " storage," like "100GB storage" or "1TB storage." So, I created my own library to handle these kinds of situations better.

Concept

In this system, we have four main database models:

  • Feature: This model handles the creation of features.
  • Ability: It manages interactions with ability-based features.
  • Usage: This model tracks the usage of quantity-based features.
  • Consumption: Responsible for managing the consumption of a usage.

When you create a new feature, you can grant a user access to that feature. Depending on the type of feature—whether it's an ability or a usage—a corresponding ability or usage record will be generated. You can then test this ability or usage by using the feature name.

Installation

  • Install via composer

composer require thomas-brillion/use-it

Usage

  • Setting up your model

Implement ThomasBrillion\UseIt\Interfaces\CanUseFeature interface in your model class and either include ThomasBrillion\UseIt\Traits\CanUseIt trait or resolve the interface on your own.

use ThomasBrillion\UseIt\Interfaces\CanUseFeature;

class User implements CanUseFeature {
   use CanUseIt;
   ...
}
  • Create Features

In order to create feature, you can use create method of FeatureService service.

There are two types of features: Ability and Quantity.

Note: Feature name must be unique.

$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$featureService = new ThomasBrillion\UseIt\Services\FeatureService($user);

$featureService->create(
   name: 'post',
   description: 'create a post',
   type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Ability,
   meta: [],
   disabled: false
)
  • Grant User to Feature

Use grantFeature method of FeatureService.

  • If feature is Ability type, Ability record will be created for user.
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$featureService = new ThomasBrillion\UseIt\Services\FeatureService($user);

$featureService->grantFeature('post', expireAt:  new DateTime('1year'))
  • if feature is Quantity type, Usage record will be created. You need pass third parameter as maximum_value of feature, optionally fourth parameter as level and fifth parameter as meta.
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$featureService = new ThomasBrillion\UseIt\Services\FeatureService($user);

$featureService->create(
   name: 'storage',
   description: 'use storage',
   type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Quantity,
   meta: [],
   disabled: false
)
$featureService->grantFeature(feature: 'storage', expireAt:  new DateTime('1year'), total:100, level: 0)

// granting multiple features
$featureService->grantFeatures(['storage','post'], expireAt:  new DateTime('1month'), total: 100, level: 0)

Note: You can create multiple usages of same feature with different maximum values and level. When usage is consumed, higher level usage will try to be consumed first.

  • Checking if user can use feature

$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$user->canUseFeature('post'); // return boolean

$user->canUseFeature(name:'storage', amount:1000);
  • Consuming usable feature

Use try method of FeatureService. Pass feature_name as first parameter and amount as second parameter if feature is quantitative type.

If feature is Quantity type, Consumption model object will be returned upon successful process. otherwise, following errors will be thrown depending on the condition.

  • Cannot find usages for the feature if feature is not found
  • Usage is expired if user has expired access to the feature
  • Usage is out of limit if user has consumed all available amount.
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$featureService = new ThomasBrillion\UseIt\Services\FeatureService($user);

$featureService->create(
   name: 'storage',
   description: 'create a post',
   type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Quantity,
)

$featureService->grantFeature('storage',  expireAt: new DateTime('1year'), total: 1000 );

$user->try('storage', amount: 10);
  • Disable/Enable Feature

$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$featureService = new ThomasBrillion\UseIt\Services\FeatureService($user);

$featureService->create(
   name: 'storage',
   description: 'use storage',
   type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Quantity,
)

$featureService->disableFeature('storage');

$featureService->enableFeature('storage');
  • Revoking access to feature

You can revoke access to feature using revokeToFeature method of FeatureService class.

$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$featureService = new ThomasBrillion\UseIt\Services\FeatureService($user);

$featureService->create(
   name: 'post',
   description: 'create a post',
   type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Ability,
)

$featureService->grantFeature('post', expireAt:  new DateTime('1month'));

$user->canUseFeature('post'); // true

$featureService->revokeToFeature('post');
$user->canUseFeature('post'); // false
  • Getting current usages of a feature

$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$user->getConsumableUsagesOfFeature('post'); // return collection of usages
  • Getting all usages of a feature

$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$user->getAllUsagesOfFeature('post'); // return collection of usages
  • Getting the current consumable usage of a feature

$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$user->getCurrentUsageOfFeature('post');
  • Getting consumptions of a feature

$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface.

$user->getConsumptionsOfFeature('post'); // return array of consumptions with key as usage id
  • Middleware

In Laravel, ThomasBrillion\UseIt\Http\Middlewares\CanUseFeatureMiddleware is automatically registered in service provider. You can use it in your route by using middleware alias can-use-feature by passing the feature name as first parameter.

Route::post('/post',[ExampleAction::class,'post'])->middleware('can-use-feature:post');

You can provide your auth guard as second parameter.

Route::post('/post',[ExampleAction::class,'post'])->middleware('can-use-feature:post,student');

To check if user can consume usage of feature, you need to pass amount input in the request.

https://example-laravel.test/post?amount=12
  • Routing

Routes are disabled by default. In order to enable it, you can either

  • set the routes config as true or
  • call this static method \ThomasBrillion\UseIt\Http\Controllers\UseItController::routes(); inside your route file ( eg routes/web.php )

There are two end-points provided by this package.

  • Checking if user can use feature - /use-it/can/{feature} In order to check if the feature can be consumed a certain amount, pass query parameters - amount in the request.

    /use-it/can/post?amount=100
    
  • Trying the feature - /use-it/try/{feature} In order to consume the feature, pass query parameters - amount and meta (optional) in the request.

    /use-it/try/post?amount=100
    
  • SoftDeletes

In order to enable SoftDelete feature, you can extend the models and follow laravel soft-delete instructions.

You also need to add deleted_at column in your migration. You can publish migrations using

php artisan vendor:publish --tag=use-it
  • Using Custom Models

You can change Feature, Ability, Usage and Consumption models by either providing custom models in config file or registering it before using it.

Your custom model must implement corresponding interface.

  • feature: ThomasBrillion\UseIt\Interfaces\Models\FeatureInterface

  • ability: ThomasBrillion\UseIt\Interfaces\Models\AbilityInterface

  • usage: ThomasBrillion\UseIt\Interfaces\Models\UsageInterface

  • consumption: ThomasBrillion\UseIt\Interfaces\Models\ConsumptionInterface

  • Method A: Config File

// configs/use-it.php
[
    'routes' => false,

    'models' => [
    
        // Change your custom model here
        'feature' => MyCustomFeatureModel::class,

        'ability' => \ThomasBrillion\UseIt\Models\Ability::class,

        'usage' => \ThomasBrillion\UseIt\Models\Usage::class,

        'consumption' => \ThomasBrillion\UseIt\Models\Consumption::class,
    ]
];
  • Method B: Manually Register Using Resolver

You can either register your custom model using ThomasBrillion\UseIt\Support\ModelResolver.

use ThomasBrillion\UseIt\Support\ModelResolver;

ModelResolver::registerModel('feature', MyCustomFeature::class);

Testing

composer run test

Bug Report

Please kindly create an issue or pull request.

License

The MIT LICENSE.

Funding

I am presently facing financial instability. Please inform me if you have any freelance projects or remote job opportunities available.

Please consider supporting me to continue contribution of open-source libraries.