Laravel Pennant

repository·1.x·Indexed 20 days ago

https://github.com/laravel/pennant

A 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.

Tokens
7.1K
Snippets
30
Records
37
Agent score
67%

What's inside Laravel Pennant

  1. Introduction to Laravel Pennant

    1.x
    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.
  2. Define feature flags with Feature::define()

    1.x

    You can define a feature flag using the Feature::define method. The definition accepts a unique name and a callback. The callback receives the current scope (typically a User instance) 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();
    });
  3. Understand how the Database Driver resolves and persists values

    1.x

    The DatabaseDriver implements a 'lazy persistence' pattern for feature flags:

    1. 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.
    2. Resolve if Missing: If no record exists, the driver executes the feature state resolver (the callback defined via Feature::define()).
    3. 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.
    4. Handle Unknown Features: If a feature is requested that has no defined resolver, the driver dispatches an UnknownFeatureResolved event and returns false.

    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.

  4. Use the ArrayDriver for in-memory feature flags

    1.x

    The ArrayDriver is 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 featureStateResolvers to calculate values for defined features.
    • Cacheable: It implements HasFlushableCache, allowing you to clear resolved states via flushCache().
    • Unknown Features: If a feature is requested that has no resolver, it dispatches an UnknownFeatureResolved event and returns false.
    // Note: This driver is typically used internally or in testing environments.
    // It implements Driver, CanListStoredFeatures, and HasFlushableCache interfaces.
  5. Publish Pennant configuration and migrations

    1.x

    You 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-config

    To publish the database migrations:

    php artisan vendor:publish --tag=pennant-migrations
    php artisan vendor:publish --tag=pennant-config
    php artisan vendor:publish --tag=pennant-migrations
  6. Check if a feature is active

    1.x

    To check if a feature is active, use the Feature::active() method for global checks, or Feature::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
    }
  7. Configure the Database Driver table and connection

    1.x

    When 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 features and the default database connection.

    To customize these, add a configuration entry for your specific store under pennant.stores.{store_name} using the table and connection keys.

    // Example configuration structure
    'pennant' => [
        'stores' => [
            'my_custom_store' => [
                'table' => 'my_feature_flags',
                'connection' => 'mysql_secondary',
            ],
        ],
    ],
  8. Use EnsureFeaturesAreActive middleware to enforce feature requirements

    1.x

    The EnsureFeaturesAreActive middleware 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 (if app.debug is enabled). However, you can customize the response behavior using the whenInactive method.

    // In your routes file
    Route::get('/premium-feature', function () {
        return 'Welcome to the premium area!';
    })->middleware(EnsureFeaturesAreActive::using(Feature::Premium));
  9. Clear Pennant caches and drivers

    1.x

    To 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();