particle-academy/laravel-fms

Laravel Feature Management System (FMS) for product feature entitlements and usage tracking

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/particle-academy/laravel-fms

v0.1 2026-01-04 21:06 UTC

This package is auto-updated.

Last update: 2026-01-04 21:23:11 UTC


README

Powered by Tynn

Laravel Feature Management System (FMS)

A standalone Laravel package for flexible feature access control and management. FMS provides simple, intuitive ways to control feature access using multiple strategies: Gates/Policies, config-based, registry-based, and database lookups.

Features

  • Multiple Access Control Strategies: Gates/Policies, config files, feature registry, or database
  • Boolean & Resource Features: Support for simple on/off features and metered resource features
  • Middleware Protection: Protect routes based on feature access
  • Facade & Helpers: Clean API via facade and global helper functions
  • Standalone Package: Zero dependencies on other packages
  • Laravel 12 Compatible: Built for Laravel 11+ and 12+

Installation

composer require particle-academy/laravel-fms

The package will auto-discover and register its service provider.

Configuration

Publish the configuration file:

php artisan vendor:publish --tag=fms-config

Define your features in config/fms.php:

return [
    'features' => [
        // Simple boolean feature
        'use-mcp' => [
            'name' => 'Use MCP',
            'description' => 'Access to MCP-powered assistants and tools.',
            'type' => 'boolean',
            'enabled' => true, // or callable: fn($user) => $user->isPremium()
        ],

        // Resource feature with limit
        'ai-tokens' => [
            'name' => 'AI Tokens',
            'description' => 'Metered AI token usage per billing period.',
            'type' => 'resource',
            'limit' => 10000, // or callable
            'usage' => fn($user) => $user->getTokenUsage(), // optional
        ],
    ],
];

Usage

Using the Facade

use ParticleAcademy\Fms\Facades\FMS;

// Check if feature is accessible
if (FMS::canAccess('use-mcp')) {
    // Feature is enabled
}

// Check if feature is enabled (alias)
if (FMS::isEnabled('use-mcp')) {
    // Feature is enabled
}

// Check if user has feature
if (FMS::hasFeature('use-mcp', $user)) {
    // User has access
}

// Get remaining quantity for resource features
$remaining = FMS::remaining('ai-tokens', $user);
if ($remaining > 0) {
    // Allow action
}

// Get all enabled features
$enabled = FMS::enabled($user);

Using Helper Functions

// Get feature manager or check feature
if (feature('use-mcp')) {
    // Feature is enabled
}

// Check feature access
if (can_access_feature('use-mcp', $user)) {
    // User has access
}

// Check if feature is enabled
if (feature_enabled('use-mcp')) {
    // Feature is enabled
}

// Get remaining quantity
$remaining = feature_remaining('ai-tokens', $user);

// Get all enabled features
$enabled = enabled_features($user);

Using Middleware

Protect routes with feature requirements:

use ParticleAcademy\Fms\Http\Middleware\RequireFeature;

Route::middleware(['auth', RequireFeature::class . ':use-mcp'])->group(function () {
    Route::get('/mcp', [McpController::class, 'index']);
});

// Multiple features (OR logic - user needs at least one)
Route::middleware(['auth', RequireFeature::class . ':feature1,feature2'])->group(function () {
    // Route protected by feature1 OR feature2
});

Using Gates/Policies

FMS automatically checks Laravel Gates if they exist:

// In AuthServiceProvider
Gate::define('use-mcp', function ($user) {
    return $user->subscription->plan === 'pro';
});

// FMS will automatically use this gate
if (FMS::canAccess('use-mcp')) {
    // Gate check passed
}

Feature Registry

Register features programmatically:

use ParticleAcademy\Fms\Services\FmsFeatureRegistry;

app(FmsFeatureRegistry::class)->register('custom-feature', [
    'name' => 'Custom Feature',
    'type' => 'boolean',
    'enabled' => fn($user) => $user->hasPermission('custom'),
]);

Access Control Strategies

FMS checks features in this order:

  1. Gates/Policies - If a Gate exists with the feature name, it's checked first
  2. Feature Registry - Checks registered features via FmsFeatureRegistry
  3. Config File - Checks config/fms.features.{feature}
  4. Database - If FeatureUsage model exists, checks database (extensible)

Resource Features

Resource features support metered usage:

'api-calls' => [
    'type' => 'resource',
    'limit' => 1000,
    'usage' => fn($user) => $user->apiCalls()->thisMonth()->count(),
    'remaining' => fn($user) => 1000 - $user->apiCalls()->thisMonth()->count(), // optional
],

Requirements

  • PHP 8.2+
  • Laravel 11+ or 12+

Testing

Run tests using Pest:

pkg laravel-fms php vendor/bin/pest

Integration with Laravel Catalog

FMS integrates seamlessly with Laravel Catalog for feature-based product management. When both packages are installed, Catalog automatically configures FMS to use Catalog's ProductFeature model.

Quick Integration Setup

  1. Install both packages:
composer require particle-academy/laravel-fms
composer require particle-academy/laravel-catalog
  1. Configure FMS features in config/fms.php:
return [
    'features' => [
        'manage-products' => [
            'name' => 'Manage Products',
            'type' => 'boolean',
            'enabled' => fn($user) => $user->hasRole('admin'),
        ],
    ],
];
  1. Use FMS in your Catalog controllers:
use ParticleAcademy\Fms\Facades\FMS;
use LaravelCatalog\Models\Product;

class ProductController extends Controller
{
    public function store(Request $request)
    {
        if (!FMS::canAccess('manage-products')) {
            abort(403);
        }
        
        $product = Product::create($request->validated());
        // ...
    }
}

Product Features Integration

Catalog's ProductFeature model works with FMS to provide feature-based access control:

use LaravelCatalog\Models\Product;
use LaravelCatalog\Models\ProductFeature;

// Attach features to products
$product = Product::find($productId);
$feature = ProductFeature::where('key', 'advanced-editing')->first();

$product->productFeatures()->attach($feature->id, [
    'enabled' => true,
    'included_quantity' => 100,
]);

// Check feature access for user's subscription
if (FMS::canAccess('advanced-editing', $user)) {
    // User has access via their subscription
}

Subscription-Based Feature Access

When integrated with Catalog, you can check feature access based on user subscriptions:

use ParticleAcademy\Fms\Facades\FMS;

// Check if user's subscription includes a feature
$user = auth()->user();
$subscription = $user->subscriptions()->active()->first();

if ($subscription) {
    $product = $subscription->product();
    
    // Check if product has feature and user has access
    foreach ($product->productFeatures as $feature) {
        if (FMS::canAccess($feature->key, $user)) {
            // Feature is available
        }
    }
}

Example: Feature-Gated Product Actions

use ParticleAcademy\Fms\Facades\FMS;
use LaravelCatalog\Facades\Catalog;

class ProductController extends Controller
{
    public function sync(Product $product)
    {
        // Check if user can sync products
        if (!FMS::canAccess('sync-products', auth()->user())) {
            abort(403, 'You do not have permission to sync products.');
        }
        
        Catalog::syncProductAndPrices($product);
        
        return redirect()->back()->with('success', 'Product synced.');
    }
    
    public function create()
    {
        // Check remaining product creations
        $remaining = FMS::remaining('product-creations', auth()->user());
        
        if ($remaining <= 0) {
            return redirect()->back()
                ->with('error', 'Product creation limit reached.');
        }
        
        return view('admin.products.create');
    }
}

Protecting Catalog Routes

Use FMS middleware to protect catalog admin routes:

use ParticleAcademy\Fms\Http\Middleware\RequireFeature;

Route::prefix('admin')->middleware([
    'auth',
    RequireFeature::class . ':manage-products'
])->group(function () {
    Route::resource('products', ProductController::class);
});

For more detailed integration examples and patterns, see INTEGRATION.md.

License

MIT