Laravel Pennant
repository·1.x·Indexed 20 days ago
https://github.com/laravel/pennantA lightweight feature flag management library for Laravel applications. It allows developers to define, activate, and toggle features globally or for specific users and entities. The library includes a @feature Blade directive, a Feature facade for state manipulation, and support for multiple storage drivers, including a database driver with lazy persistence and an in-memory ArrayDriver for testing.
What's inside Laravel Pennant
- Laravel Pennant is a simple, lightweight library designed for managing feature flags within your application. It allows you to easily toggle features on or off for specific users or across your entire application.
Define feature flags with Feature::define()
1.xYou can define a feature flag using the
Feature::definemethod. The definition accepts a unique name and a callback. The callback receives the current scope (typically aUserinstance) and should return a boolean indicating whether the feature is active for that scope.use Laravel//Pennant//Feature; Feature::define('new-dashboard', function (User $user) { return $user->isAdmin(); });Understand how the Database Driver resolves and persists values
1.xThe
DatabaseDriverimplements a 'lazy persistence' pattern for feature flags:- Check Storage: When you request a feature value, the driver first checks the database for a record matching the feature name and the serialized scope.
- Resolve if Missing: If no record exists, the driver executes the feature state resolver (the callback defined via
Feature::define()). - Persist Resolved Value: If the resolver returns a value (and is not an 'unknown feature'), the driver automatically inserts that value into the database so that subsequent checks do not need to re-run the resolver logic.
- Handle Unknown Features: If a feature is requested that has no defined resolver, the driver dispatches an
UnknownFeatureResolvedevent and returnsfalse.
This ensures that expensive resolver logic (like API calls or complex calculations) is only executed once per scope, after which the result is cached in your database.
Use the ArrayDriver for in-memory feature flags
1.xThe
ArrayDriveris an in-memory implementation of the Pennant driver interface. It is useful for testing or scenarios where feature flag states do not need to persist between requests. It stores resolved feature states in a local array and uses provided resolvers to determine values for features that haven't been explicitly set.Key characteristics:
- In-memory storage: All data is lost when the process ends.
- Resolver-based: It relies on
featureStateResolversto calculate values for defined features. - Cacheable: It implements
HasFlushableCache, allowing you to clear resolved states viaflushCache(). - Unknown Features: If a feature is requested that has no resolver, it dispatches an
UnknownFeatureResolvedevent and returnsfalse.
// Note: This driver is typically used internally or in testing environments. // It implements Driver, CanListStoredFeatures, and HasFlushableCache interfaces.Publish Pennant configuration and migrations
1.xYou can publish the Pennant configuration file and database migrations to your application using the following Artisan commands:
To publish the configuration file:
php artisan vendor:publish --tag=pennant-configTo publish the database migrations:
php artisan vendor:publish --tag=pennant-migrationsphp artisan vendor:publish --tag=pennant-config php artisan vendor:publish --tag=pennant-migrationsCommon pitfalls when using Pennant
1.xWhen implementing feature flags, be mindful of the following:
- Missing Scopes: Forgetting to scope features to specific users or entities when the feature logic depends on them.
- Naming Conventions: Not following existing naming conventions for feature flags within your application.
Activate features manually
1.xYou can explicitly activate a feature flag using
Feature::activate(). This can be done globally or scoped to a specific user or entity usingFeature::for($scope)->activate().// Global activation Feature::activate('new-dashboard'); // Scoped activation Feature::for($user)->activate('new-dashboard');Check if a feature is active
1.xTo check if a feature is active, use the
Feature::active()method for global checks, orFeature::for($scope)->active()to check if a feature is active for a specific user or entity.// Global check if (Feature::active('new-dashboard')) { // Feature is active } // Scoped check (e.g., for a specific user) if (Feature::for($user)->active('new-dashboard')) { // Feature is active for this user }Use the @feature Blade directive
1.xLaravel Pennant provides a
@featureBlade directive to conditionally render UI elements based on whether a feature flag is active. It supports an@elseblock for fallback content.@feature('new-dashboard') <x-new-dashboard /> @else <x-old-dashboard /> @endfeatureConfigure the Database Driver table and connection
1.xWhen using the database driver for Pennant, you can customize the table name and the database connection used for your feature flags via your application's configuration. By default, the driver uses a table named
featuresand the default database connection.To customize these, add a configuration entry for your specific store under
pennant.stores.{store_name}using thetableandconnectionkeys.// Example configuration structure 'pennant' => [ 'stores' => [ 'my_custom_store' => [ 'table' => 'my_feature_flags', 'connection' => 'mysql_secondary', ], ], ],Use EnsureFeaturesAreActive middleware to enforce feature requirements
1.xThe
EnsureFeaturesAreActivemiddleware ensures that a specific set of features are enabled for the current request. If any of the required features are inactive, the middleware will interrupt the request.By default, if a feature is inactive, the middleware will
abort(400)with an error message (ifapp.debugis enabled). However, you can customize the response behavior using thewhenInactivemethod.// In your routes file Route::get('/premium-feature', function () { return 'Welcome to the premium area!'; })->middleware(EnsureFeaturesAreActive::using(Feature::Premium));Clear Pennant caches and drivers
1.xTo manage memory or reset state during testing, you can clear resolved stores or flush driver caches.
flushCache(): Tells all currently resolved stores to flush their internal caches.forgetDriver($name): Removes a specific store from the manager's internal cache.forgetDrivers(): Removes all resolved stores.
// Flush caches for all stores Feature::flushCache(); // Forget a specific store Feature::forgetDriver('database'); // Forget all stores Feature::forgetDrivers();