thomas-brillion / use-it
Easy to use Laravel Features, Abilities, Usages and Consumptions
Fund package maintenance!
w99910
buy.stripe.com/eVa4hXaHdc0t1Nu144
Installs: 20
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/thomas-brillion/use-it
Requires
- php: ^8.1|^8.2|^8.3
- illuminate/database: ^10.10.0|^11.0
- illuminate/http: ^11.6
- illuminate/support: ^10.10.0|^11.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.54
- illuminate/testing: ^10.10.0|^11.0
- pestphp/pest: ^1.20|^2.34
README
Table Of Contents
- Introduction
- Concept
- Installation
- Usage
- Middleware
- SoftDeletes
- Custom Models
- Testing
- Bug Report
- License
- Funding
FeatureGroupis introducedv0.2.0. It is similar toRoleinroles-permissionsconcept. You can assign multiplefeaturesto thefeature-groupand then those features will be automatically attached to the user if user is given access to thatfeature-group.
Introduction
While there are many packages in Laravel like Laravel Pennant (for feature flags) and Spatie Permission (for managing user permissions and roles), I would like to create a feature that is not only an ability or permission but also a consumable one.
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."
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.
- FeatureGroup: Responsible for handling feature groups
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
composer require thomas-brillion/use-it
Publish the migration presets.
php artisan vendor:publish --tag=use-it-migrations
Note: If you are using feature group on different table other than users, please create a pivot table and change the table in the relationship such as
public function featureGroups(): BelongsToMany { return $this->belongsToMany(ThomasBrillion\UseIt\Support\ModelResolver::getFeatureGroupModel(), 'your_pivot_table_name'); }
Please consult laravel documentation for more details.
Implement either ThomasBrillion\UseIt\Interfaces\CanUseFeature or ThomasBrillion\UseIt\Interfaces\CanUseFeatureGroup interface in your model class and
either include ThomasBrillion\UseIt\Traits\CanUseIt trait or resolve the interface on your own.
It is not mandatory to implement
ThomasBrillion\UseIt\Interfaces\CanUseFeatureGroupinterface if you don't want to use feature group.
use ThomasBrillion\UseIt\Interfaces\CanUseFeature; class User implements CanUseFeature { use CanUseIt; ... }
Usage
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.
ThomasBrillion\UseIt\Services\FeatureService::create( name: 'post', description: 'create a post', type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Ability, meta: [], disabled: false )
Use grantFeature method of FeatureService.
- If feature is
Abilitytype,Abilityrecord will be created for user.
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface. ThomasBrillion\UseIt\Services\FeatureService::of($user)->grantFeature('post', expireAt: new DateTime('1year'))
- if feature is
Quantitytype,Usagerecord will be created. You need pass third parameter asmaximum_valueof feature, optionally fourth parameter asleveland fifth parameter asmeta.
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface. ThomasBrillion\UseIt\Services\FeatureService::create( name: 'storage', description: 'use storage', type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Quantity, meta: [], disabled: false ) ThomasBrillion\UseIt\Services\FeatureService::of($user)->grantFeature(feature: 'storage', expireAt: new DateTime('1year'), total: 100, level: 0) // granting multiple features ThomasBrillion\UseIt\Services\FeatureService::of($user)->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.
You can use
canUseFeatureto check if user can use all of provided feature/s.
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface. $user->canUseFeature('post'); $user->canUseFeature('storage', amount:1000); $user->canUseFeature('upload,storage', amount: 1000); $user->canUseFeature(['upload', 'storage'], amount: 1000);
- `canUseAnyFeature to check if user can use any of provided feature/s. You can pass either comma-separated string or string or array.
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface. $user->canUseAnyFeature('post'); $user->canUseAnyFeature('post,upload,comment'); $user->canUseAnyFeature(['post','upload','comment'], amount:1000);
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 featureif feature is not foundUsage is expiredif user has expired access to the featureUsage is out of limitif 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); ThomasBrillion\UseIt\Services\FeatureService::create( name: 'storage', description: 'create a post', type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Quantity, ) ThomasBrillion\UseIt\Services\FeatureService::of($user)->grantFeature('storage', expireAt: new DateTime('1year'), total: 1000 ); $user->try('storage', amount: 10);
You can toggle accessiblity to the feature.
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface. ThomasBrillion\UseIt\Services\FeatureService::create( name: 'storage', description: 'use storage', type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Quantity, ) ThomasBrillion\UseIt\Services\FeatureService::disableFeature('storage'); ThomasBrillion\UseIt\Services\FeatureService::enableFeature('storage');
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); ThomasBrillion\UseIt\Services\FeatureService::create( name: 'post', description: 'create a post', type: \ThomasBrillion\UseIt\Support\Enums\FeatureType::Ability, ) ThomasBrillion\UseIt\Services\FeatureService::of($user)->grantFeature('post', expireAt: new DateTime('1month')); $user->canUseFeature('post'); // true ThomasBrillion\UseIt\Services\FeatureService::of($user)->revokeToFeature('post'); $user->canUseFeature('post'); // false
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface. $user->getConsumableUsagesOfFeature('post'); // return collection of usages
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface. $user->getAllUsagesOfFeature('post'); // return collection of usages
$user = User::first(); // or any eloquent model which either uses `CanUseIt` trait or implements `CanUseFeature` interface. $user->getCurrentUsageOfFeature('post');
$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
A feature group is a logical grouping of features, offering a centralized way to access and revoke features instead of interacting with a feature manually.
- Creating feature group
$featureGroup = ThomasBrillion\UseIt\Models\FeatureGroup::create( 'premium-plan', 'gives access to premium features' );
- Create new features or add existing features to feature group
I recommand you to set preset data for Quantity type feature such as total or expireInSeconds.
$fourTeamMembersFeature = FeatureService::create('mini-team', 'add four team members', FeatureType::Quantity, total: 4, expireInSeconds: 60 * 60 * 24 * 30); FeatureGroupService::addFeatures($featureGroup, [ $fourTeamMembersFeature, 'can-post' ]);
- Grant user to that feature group.
$user = User::first(); FeatureGroupService::of($user)->grantFeatureGroup('premium-plan'); expect($user->canUseFeature('mini-team', 1))->toBeTrue(); expect($user->canUseFeature('can-post'))->toBeTrue();
- Checking if user has feature group
$user->hasFeatureGroup('premium-plan'); // return boolean.
- Revoke feature group
FeatureGroupService::of($user)->revokeFeatureGroup('premium-plan');
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 and can-use-any-feature by passing the feature name as first
parameter.
Route::post('/post',[ExampleAction::class,'post'])->middleware('can-use-feature:post');
You can provide multiple feature names by separating with comma.
Route::post('/post',[ExampleAction::class,'post'])->middleware('can-use-feature:post,comment,upload'); Route::post('/post',[ExampleAction::class,'post'])->middleware('can-use-any-feature:post,comment');
Note: To check if user can consume usage of feature, you need to pass
amountinput in the request.
https://example-laravel.test/post?amount=12
Routes are disabled by default. In order to enable it, you can either
- set the
routesconfig astrueor - call this static
method
\ThomasBrillion\UseIt\Http\Controllers\UseItController::routes();inside your route file ( egroutes/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 -amountin the request./use-it/can/post?amount=100 -
Trying the feature -
/use-it/try/{feature}In order to consume the feature, pass query parameters -amountandmeta(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
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 -
featureGroup:
ThomasBrillion\UseIt\Interfaces\Models\FeatureGroupInterface -
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, 'feature-group' => \ThomasBrillion\UseIt\Models\FeatureGroup::class, 'ability' => \ThomasBrillion\UseIt\Models\Ability::class, 'usage' => \ThomasBrillion\UseIt\Models\Usage::class, 'consumption' => \ThomasBrillion\UseIt\Models\Consumption::class, ] ];
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
Consider supporting me or letting me know if you have any freelance projects or remote job opportunities available.
Please consider supporting me to continue contribution of open-source libraries.
