gwhitdev/entitlements-for-laravel
Composer 安装命令:
composer require gwhitdev/entitlements-for-laravel
包简介
Plan-based feature entitlements for Laravel — the layer between Cashier (who pays) and your app (what paying unlocks). Facade: Tessera. Part of the Keel line.
README 文档
README
Plan-based feature entitlements for Laravel — the layer between Cashier (who pays) and your app (what paying unlocks). Facade: Tessera. Part of the Keel line.
Cashier gives you subscriptions. Pennant gives you feature flags. Neither answers the question every SaaS actually asks: "does this user's plan include this feature?" That's an entitlement, and this package is the engine for it — a memoized resolution cascade over plan mappings, per-user grants, and membership state, with the seams to swap billing provider and feature catalog without rewrites.
$user->hasFeature('advanced_reporting'); // trait on your User model Tessera::has($user, 'advanced_reporting'); // or the facade Route::get('/reports', ...)->middleware('feature:advanced_reporting');
Status
Alpha — v0.2.0. Stages 1–3 are complete (core engine, DX layer, declarative dependencies), a security hardening pass has shipped, and the optional Pennant bridge is available. The package is usable — install it, kick the tyres, and open issues. Expect API stability from here but no production-stability guarantee until v1.0.
Requirements
- PHP 8.2+
- Laravel 11, 12, or 13
- Cashier (optional) — the default
PlanResolverreads Stripe prices through it; the seam supports other billers.
Installation
composer require gwhitdev/entitlements-for-laravel
Publish the config and migrations, then migrate:
php artisan entitlements:install # publishes config + migrations, prints next steps
php artisan migrate
Add the trait to your User model:
use Entitlements\Concerns\HasFeatures; class User extends Authenticatable { use HasFeatures; }
Quick Start
-
Define your features. Scaffold a case onto your feature enum:
php artisan entitlements:make AdvancedReporting
-
Map plans to features. With the
configplan store, declare the mapping as code inconfig/entitlements.php(the defaultdatabasestore keeps it runtime-editable instead):'plan_store' => 'config', 'plans' => [ 'price_pro_monthly' => ['advanced_reporting', 'team_seats'], ],
-
Check entitlement anywhere — trait, facade, middleware, or Blade:
if ($user->hasFeature('advanced_reporting')) { // ... }
The gate resolves a cascade — admin override → per-user grant → plan mapping (via the active subscription's price) — and memoizes the result per request.
Usage
On the User model (HasFeatures trait)
$user->hasFeature('advanced_reporting'); // bool — is the user entitled? $user->features(); // string[] — every key the user is entitled to $user->grantFeature('advanced_reporting'); // direct grant, an override above their plan
Via the facade
use Entitlements\Facades\Tessera; Tessera::has($user, 'advanced_reporting'); // bool Tessera::entitlements($user); // string[]
Route middleware
The feature: alias aborts with 403 unless the authenticated user is entitled:
Route::get('/reports', ReportController::class) ->middleware('feature:advanced_reporting');
Blade directive
@feature('advanced_reporting') <a href="/reports">Advanced reporting</a> @endfeature
Pennant bridge (optional)
If your codebase already uses Laravel Pennant, you can expose all entitlements as Pennant features so existing Feature:: check sites keep working:
// In AppServiceProvider::boot() use Entitlements\Bridge\PennantBridge; PennantBridge::register();
After calling this, Pennant's standard API resolves through the entitlement cascade:
use Laravel\Pennant\Feature; Feature::for($user)->active('advanced_reporting'); // delegates to Tessera
Pennant's own storage and caching are bypassed — resolution always goes through the cascade, so grants and plan changes are reflected immediately.
Artisan commands
| Command | What it does |
|---|---|
entitlements:install |
Publishes config + migrations and prints next steps. |
entitlements:make <Name> |
Adds a case to your feature enum. |
entitlements:lint |
Checks that every plan → feature mapping satisfies its declared prerequisites. |
Configuration
Every moving part is a swappable seam, bound from config/entitlements.php:
return [ // The public resolution gate (implements Entitlements\Contracts\FeatureGate). 'gate' => \Entitlements\Gate\CascadingFeatureGate::class, // The billing seam. Default reads Cashier's Stripe price; swap for // Paddle / Lemon Squeezy by pointing this at your own PlanResolver. 'resolver' => \Entitlements\Resolvers\StripePlanResolver::class, // The catalog seam. Default is enum-backed; ConfigFeatureCatalog is also available. 'catalog' => \Entitlements\Catalog\EnumFeatureCatalog::class, 'enum' => \App\Enums\Feature::class, // Where plan → feature mappings live: 'database' (runtime-editable) or 'config'. 'plan_store' => 'database', 'plans' => [], // Off by default (fail-closed). When enabled, an admin is entitled to every feature; prefer // defining isEntitlementAdmin() on your User model over relying on a raw is_admin column. 'admin_override' => false, ];
FeatureGate is the only public surface — the cascade and memoization live behind it, which keeps the package headless and the seams independently swappable.
What this package is — and isn't
Entitlements solves one problem precisely: given a user and a feature key, is that user entitled to it? It answers that by reading your plan → feature mapping, per-user grants, and Cashier's active subscription state — then getting out of the way.
It is deliberately not a billing engine and not a usage tracker:
| Concern | Owner |
|---|---|
| Does this plan include this feature? (access gating) | Entitlements ✅ |
| Per-user grants / overrides above a plan | Entitlements ✅ |
| Consumable quotas — counting, decrementing, resetting usage | Stripe Meters or a dedicated quota package |
| Overage billing — charging when an allocation is exceeded | Cashier / your billing provider |
| Subscription lifecycle — trials, grace periods, renewal webhooks | Cashier (Entitlements reads isActive() through the PlanResolver seam) |
If you need quota tracking on top of access gating, the roadmap includes a consumable feature type (allocations on plan_features, a usage table, consume()/canConsume()/remainingUsage() on the trait) as a possible future stage — but it is not built yet. The access-gating core is stable and useful without it.
Design at a glance
- Free MIT core (this package) + a paid admin UI (drag-drop plan ↔ feature mapping) sold separately.
- Seams, defaulted:
PlanResolver(Cashier by default, billing-agnostic underneath),FeatureCatalog(enum by default; config/DB drivers),FeatureGate(the only public surface — keeps it headless). - Pennant bridge (optional): expose entitlements as Pennant features for a familiar check API.
- Built for artisans and agents: ships with
AGENTS.mdso an AI assistant can gate a feature correctly in one prompt.
Testing
composer test
The suite runs against PHP 8.2–8.4 and Laravel 11–12 in CI. A dependency audit job runs alongside it on every push.
Security
Please email garethwhitleychard@gmail.com to report vulnerabilities rather than opening a public issue. You'll receive a response within 72 hours.
License
MIT.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 4
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-24