# Laravel Pennant * Introduction * Installation * Configuration * Defining Features * Class Based Features * Checking Features * Conditional Execution * The `HasFeatures` Trait * Blade Directive * Middleware * Intercepting Feature Checks * In-Memory Cache * Scope * Specifying the Scope * Default Scope * Nullable Scope * Identifying Scope * Serializing Scope * Rich Feature Values * Retrieving Multiple Features * Eager Loading * Updating Values * Bulk Updates * Purging Features * Testing * Adding Custom Pennant Drivers * Implementing the Driver * Registering the Driver * Defining Features Externally * Events ## Introduction [Laravel Pennant](https://github.com/laravel/pennant) is a simple and light- weight feature flag package - without the cruft. Feature flags enable you to incrementally roll out new application features with confidence, A/B test new interface designs, complement a trunk-based development strategy, and much more. ## Installation First, install Pennant into your project using the Composer package manager: 1composer require laravel/pennant composer require laravel/pennant Next, you should publish the Pennant configuration and migration files using the `vendor:publish` Artisan command: 1php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider" php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider" Finally, you should run your application's database migrations. This will create a `features` table that Pennant uses to power its `database` driver: 1php artisan migrate php artisan migrate ## Configuration After publishing Pennant's assets, its configuration file will be located at `config/pennant.php`. This configuration file allows you to specify the default storage mechanism that will be used by Pennant to store resolved feature flag values. Pennant includes support for storing resolved feature flag values in an in- memory array via the `array` driver. Or, Pennant can store resolved feature flag values persistently in a relational database via the `database` driver, which is the default storage mechanism used by Pennant. ## Defining Features To define a feature, you may use the `define` method offered by the `Feature` facade. You will need to provide a name for the feature, as well as a closure that will be invoked to resolve the feature's initial value. Typically, features are defined in a service provider using the `Feature` facade. The closure will receive the "scope" for the feature check. Most commonly, the scope is the currently authenticated user. In this example, we will define a feature for incrementally rolling out a new API to our application's users: 1 match (true) { 18 $user->isInternalTeamMember() => true, 19 $user->isHighTrafficCustomer() => false, 20 default => Lottery::odds(1 / 100), 21 }); 22 } 23} match (true) { $user->isInternalTeamMember() => true, $user->isHighTrafficCustomer() => false, default => Lottery::odds(1 / 100), }); } } As you can see, we have the following rules for our feature: * All internal team members should be using the new API. * Any high traffic customers should not be using the new API. * Otherwise, the feature should be randomly assigned to users with a 1 in 100 chance of being active. The first time the `new-api` feature is checked for a given user, the result of the closure will be stored by the storage driver. The next time the feature is checked against the same user, the value will be retrieved from storage and the closure will not be invoked. For convenience, if a feature definition only returns a lottery, you may omit the closure completely: 1Feature::define('site-redesign', Lottery::odds(1, 1000)); Feature::define('site-redesign', Lottery::odds(1, 1000)); ### Class Based Features Pennant also allows you to define class-based features. Unlike closure-based feature definitions, there is no need to register a class-based feature in a service provider. To create a class-based feature, you may invoke the `pennant:feature` Artisan command. By default, the feature class will be placed in your application's `app/Features` directory: 1php artisan pennant:feature NewApi php artisan pennant:feature NewApi When writing a feature class, you only need to define a `resolve` method, which will be invoked to resolve the feature's initial value for a given scope. Again, the scope will typically be the currently authenticated user: 1isInternalTeamMember() => true, 17 $user->isHighTrafficCustomer() => false, 18 default => Lottery::odds(1 / 100), 19 }; 20 } 21} isInternalTeamMember() => true, $user->isHighTrafficCustomer() => false, default => Lottery::odds(1 / 100), }; } } If you would like to manually resolve an instance of a class-based feature, you may invoke the `instance` method on the `Feature` facade: 1use Illuminate\Support\Facades\Feature; 2  3$instance = Feature::instance(NewApi::class); use Illuminate\Support\Facades\Feature; $instance = Feature::instance(NewApi::class); Feature classes are resolved via the [container](/docs/12.x/container), so you may inject dependencies into the feature class's constructor when needed. #### Customizing the Stored Feature Name By default, Pennant will store the feature class's fully qualified class name. If you would like to decouple the stored feature name from the application's internal structure, you may specify a `$name` property on the feature class. The value of this property will be stored in place of the class name: 1resolveNewApiResponse($request) 18 : $this->resolveLegacyApiResponse($request); 19 } 20  21 // ... 22} resolveNewApiResponse($request) : $this->resolveLegacyApiResponse($request); } // ... } Although features are checked against the currently authenticated user by default, you may easily check the feature against another user or scope. To accomplish this, use the `for` method offered by the `Feature` facade: 1return Feature::for($user)->active('new-api') 2 ? $this->resolveNewApiResponse($request) 3 : $this->resolveLegacyApiResponse($request); return Feature::for($user)->active('new-api') ? $this->resolveNewApiResponse($request) : $this->resolveLegacyApiResponse($request); Pennant also offers some additional convenience methods that may prove useful when determining if a feature is active or not: 1// Determine if all of the given features are active... 2Feature::allAreActive(['new-api', 'site-redesign']); 3  4// Determine if any of the given features are active... 5Feature::someAreActive(['new-api', 'site-redesign']); 6  7// Determine if a feature is inactive... 8Feature::inactive('new-api'); 9  10// Determine if all of the given features are inactive... 11Feature::allAreInactive(['new-api', 'site-redesign']); 12  13// Determine if any of the given features are inactive... 14Feature::someAreInactive(['new-api', 'site-redesign']); // Determine if all of the given features are active... Feature::allAreActive(['new-api', 'site-redesign']); // Determine if any of the given features are active... Feature::someAreActive(['new-api', 'site-redesign']); // Determine if a feature is inactive... Feature::inactive('new-api'); // Determine if all of the given features are inactive... Feature::allAreInactive(['new-api', 'site-redesign']); // Determine if any of the given features are inactive... Feature::someAreInactive(['new-api', 'site-redesign']); When using Pennant outside of an HTTP context, such as in an Artisan command or a queued job, you should typically explicitly specify the feature's scope. Alternatively, you may define a default scope that accounts for both authenticated HTTP contexts and unauthenticated contexts. #### Checking Class Based Features For class-based features, you should provide the class name when checking the feature: 1resolveNewApiResponse($request) 19 : $this->resolveLegacyApiResponse($request); 20 } 21  22 // ... 23} resolveNewApiResponse($request) : $this->resolveLegacyApiResponse($request); } // ... } ### Conditional Execution The `when` method may be used to fluently execute a given closure if a feature is active. Additionally, a second closure may be provided and will be executed if the feature is inactive: 1 $this->resolveNewApiResponse($request), 19 fn () => $this->resolveLegacyApiResponse($request), 20 ); 21 } 22  23 // ... 24} $this->resolveNewApiResponse($request), fn () => $this->resolveLegacyApiResponse($request), ); } // ... } The `unless` method serves as the inverse of the `when` method, executing the first closure if the feature is inactive: 1return Feature::unless(NewApi::class, 2 fn () => $this->resolveLegacyApiResponse($request), 3 fn () => $this->resolveNewApiResponse($request), 4); return Feature::unless(NewApi::class, fn () => $this->resolveLegacyApiResponse($request), fn () => $this->resolveNewApiResponse($request), ); ### The `HasFeatures` Trait Pennant's `HasFeatures` trait may be added to your application's `User` model (or any other model that has features) to provide a fluent, convenient way to check features directly from the model: 1features()->active('new-api')) { 2 // ... 3} if ($user->features()->active('new-api')) { // ... } Of course, the `features` method provides access to many other convenient methods for interacting with features: 1// Values... 2$value = $user->features()->value('purchase-button') 3$values = $user->features()->values(['new-api', 'purchase-button']); 4  5// State... 6$user->features()->active('new-api'); 7$user->features()->allAreActive(['new-api', 'server-api']); 8$user->features()->someAreActive(['new-api', 'server-api']); 9  10$user->features()->inactive('new-api'); 11$user->features()->allAreInactive(['new-api', 'server-api']); 12$user->features()->someAreInactive(['new-api', 'server-api']); 13  14// Conditional execution... 15$user->features()->when('new-api', 16 fn () => /* ... */, 17 fn () => /* ... */, 18); 19  20$user->features()->unless('new-api', 21 fn () => /* ... */, 22 fn () => /* ... */, 23); // Values... $value = $user->features()->value('purchase-button') $values = $user->features()->values(['new-api', 'purchase-button']); // State... $user->features()->active('new-api'); $user->features()->allAreActive(['new-api', 'server-api']); $user->features()->someAreActive(['new-api', 'server-api']); $user->features()->inactive('new-api'); $user->features()->allAreInactive(['new-api', 'server-api']); $user->features()->someAreInactive(['new-api', 'server-api']); // Conditional execution... $user->features()->when('new-api', fn () => /* ... */, fn () => /* ... */, ); $user->features()->unless('new-api', fn () => /* ... */, fn () => /* ... */, ); ### Blade Directive To make checking features in Blade a seamless experience, Pennant offers the `@feature` and `@featureany` directive: 1@feature('site-redesign') 2 3@else 4 5@endfeature 6  7@featureany(['site-redesign', 'beta']) 8 9@endfeatureany @feature('site-redesign') @else @endfeature @featureany(['site-redesign', 'beta']) @endfeatureany ### Middleware Pennant also includes a [middleware](/docs/12.x/middleware) that may be used to verify the currently authenticated user has access to a feature before a route is even invoked. You may assign the middleware to a route and specify the features that are required to access the route. If any of the specified features are inactive for the currently authenticated user, a `400 Bad Request` HTTP response will be returned by the route. Multiple features may be passed to the static `using` method. 1use Illuminate\Support\Facades\Route; 2use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; 3  4Route::get('/api/servers', function () { 5 // ... 6})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api')); use Illuminate\Support\Facades\Route; use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; Route::get('/api/servers', function () { // ... })->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api')); #### Customizing the Response If you would like to customize the response that is returned by the middleware when one of the listed features is inactive, you may use the `whenInactive` method provided by the `EnsureFeaturesAreActive` middleware. Typically, this method should be invoked within the `boot` method of one of your application's service providers: 1use Illuminate\Http\Request; 2use Illuminate\Http\Response; 3use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; 4  5/** 6 * Bootstrap any application services. 7 */ 8public function boot(): void 9{ 10 EnsureFeaturesAreActive::whenInactive( 11 function (Request $request, array $features) { 12 return new Response(status: 403); 13 } 14 ); 15  16 // ... 17} use Illuminate\Http\Request; use Illuminate\Http\Response; use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; /** * Bootstrap any application services. */ public function boot(): void { EnsureFeaturesAreActive::whenInactive( function (Request $request, array $features) { return new Response(status: 403); } ); // ... } ### Intercepting Feature Checks Sometimes it can be useful to perform some in-memory checks before retrieving the stored value of a given feature. Imagine you are developing a new API behind a feature flag and want the ability to disable the new API without losing any of the resolved feature values in storage. If you notice a bug in the new API, you could easily disable it for everyone except internal team members, fix the bug, and then re-enable the new API for the users that previously had access to the feature. You can achieve this with a class-based feature's `before` method. When present, the `before` method is always run in-memory before retrieving the value from storage. If a non-`null` value is returned from the method, it will be used in place of the feature's stored value for the duration of the request: 1isInternalTeamMember(); 18 } 19 } 20  21 /** 22 * Resolve the feature's initial value. 23 */ 24 public function resolve(User $user): mixed 25 { 26 return match (true) { 27 $user->isInternalTeamMember() => true, 28 $user->isHighTrafficCustomer() => false, 29 default => Lottery::odds(1 / 100), 30 }; 31 } 32} isInternalTeamMember(); } } /** * Resolve the feature's initial value. */ public function resolve(User $user): mixed { return match (true) { $user->isInternalTeamMember() => true, $user->isHighTrafficCustomer() => false, default => Lottery::odds(1 / 100), }; } } You could also use this feature to schedule the global rollout of a feature that was previously behind a feature flag: 1isInternalTeamMember(); 17 } 18  19 if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) { 20 return true; 21 } 22 } 23  24 // ... 25} isInternalTeamMember(); } if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) { return true; } } // ... } ### In-Memory Cache When checking a feature, Pennant will create an in-memory cache of the result. If you are using the `database` driver, this means that re-checking the same feature flag within a single request will not trigger additional database queries. This also ensures that the feature has a consistent result for the duration of the request. If you need to manually flush the in-memory cache, you may use the `flushCache` method offered by the `Feature` facade: 1Feature::flushCache(); Feature::flushCache(); ## Scope ### Specifying the Scope As discussed, features are typically checked against the currently authenticated user. However, this may not always suit your needs. Therefore, it is possible to specify the scope you would like to check a given feature against via the `Feature` facade's `for` method: 1return Feature::for($user)->active('new-api') 2 ? $this->resolveNewApiResponse($request) 3 : $this->resolveLegacyApiResponse($request); return Feature::for($user)->active('new-api') ? $this->resolveNewApiResponse($request) : $this->resolveLegacyApiResponse($request); Of course, feature scopes are not limited to "users". Imagine you have built a new billing experience that you are rolling out to entire teams rather than individual users. Perhaps you would like the oldest teams to have a slower rollout than the newer teams. Your feature resolution closure might look something like the following: 1use App\Models\Team; 2use Illuminate\Support\Carbon; 3use Illuminate\Support\Lottery; 4use Laravel\Pennant\Feature; 5  6Feature::define('billing-v2', function (Team $team) { 7 if ($team->created_at->isAfter(new Carbon('1st Jan, 2023'))) { 8 return true; 9 } 10  11 if ($team->created_at->isAfter(new Carbon('1st Jan, 2019'))) { 12 return Lottery::odds(1 / 100); 13 } 14  15 return Lottery::odds(1 / 1000); 16}); use App\Models\Team; use Illuminate\Support\Carbon; use Illuminate\Support\Lottery; use Laravel\Pennant\Feature; Feature::define('billing-v2', function (Team $team) { if ($team->created_at->isAfter(new Carbon('1st Jan, 2023'))) { return true; } if ($team->created_at->isAfter(new Carbon('1st Jan, 2019'))) { return Lottery::odds(1 / 100); } return Lottery::odds(1 / 1000); }); You will notice that the closure we have defined is not expecting a `User`, but is instead expecting a `Team` model. To determine if this feature is active for a user's team, you should pass the team to the `for` method offered by the `Feature` facade: 1if (Feature::for($user->team)->active('billing-v2')) { 2 return redirect('/billing/v2'); 3} 4  5// ... if (Feature::for($user->team)->active('billing-v2')) { return redirect('/billing/v2'); } // ... ### Default Scope It is also possible to customize the default scope Pennant uses to check features. For example, maybe all of your features are checked against the currently authenticated user's team instead of the user. Instead of having to call `Feature::for($user->team)` every time you check a feature, you may instead specify the team as the default scope. Typically, this should be done in one of your application's service providers: 1 Auth::user()?->team); 17  18 // ... 19 } 20} Auth::user()?->team); // ... } } If no scope is explicitly provided via the `for` method, the feature check will now use the currently authenticated user's team as the default scope: 1Feature::active('billing-v2'); 2  3// Is now equivalent to... 4  5Feature::for($user->team)->active('billing-v2'); Feature::active('billing-v2'); // Is now equivalent to... Feature::for($user->team)->active('billing-v2'); ### Nullable Scope If the scope you provide when checking a feature is `null` and the feature's definition does not support `null` via a nullable type or by including `null` in a union type, Pennant will automatically return `false` as the feature's result value. So, if the scope you are passing to a feature is potentially `null` and you want the feature's value resolver to be invoked, you should account for that in your feature's definition. A `null` scope may occur if you check a feature within an Artisan command, queued job, or unauthenticated route. Since there is usually not an authenticated user in these contexts, the default scope will be `null`. If you do not always explicitly specify your feature scope then you should ensure the scope's type is "nullable" and handle the `null` scope value within your feature definition logic: 1use App\Models\User; 2use Illuminate\Support\Lottery; 3use Laravel\Pennant\Feature; 4  5Feature::define('new-api', fn (User $user) => match (true) { 6Feature::define('new-api', fn (User|null $user) => match (true) { 7 $user === null => true, 8 $user->isInternalTeamMember() => true, 9 $user->isHighTrafficCustomer() => false, 10 default => Lottery::odds(1 / 100), 11}); use App\Models\User; use Illuminate\Support\Lottery; use Laravel\Pennant\Feature; Feature::define('new-api', fn (User $user) => match (true) { Feature::define('new-api', fn (User|null $user) => match (true) { $user === null => true, $user->isInternalTeamMember() => true, $user->isHighTrafficCustomer() => false, default => Lottery::odds(1 / 100), }); ### Identifying Scope Pennant's built-in `array` and `database` storage drivers know how to properly store scope identifiers for all PHP data types as well as Eloquent models. However, if your application utilizes a third-party Pennant driver, that driver may not know how to properly store an identifier for an Eloquent model or other custom types in your application. In light of this, Pennant allows you to format scope values for storage by implementing the `FeatureScopeable` contract on the objects in your application that are used as Pennant scopes. For example, imagine you are using two different feature drivers in a single application: the built-in `database` driver and a third-party "Flag Rocket" driver. The "Flag Rocket" driver does not know how to properly store an Eloquent model. Instead, it requires a `FlagRocketUser` instance. By implementing the `toFeatureIdentifier` defined by the `FeatureScopeable` contract, we can customize the storable scope value provided to each driver used by our application: 1 $this, 18 'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id), 19 }; 20 } 21} $this, 'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id), }; } } ### Serializing Scope By default, Pennant will use a fully qualified class name when storing a feature associated with an Eloquent model. If you are already using an [Eloquent morph map](/docs/12.x/eloquent-relationships#custom-polymorphic- types), you may choose to have Pennant also use the morph map to decouple the stored feature from your application structure. To achieve this, after defining your Eloquent morph map in a service provider, you may invoke the `Feature` facade's `useMorphMap` method: 1use Illuminate\Database\Eloquent\Relations\Relation; 2use Laravel\Pennant\Feature; 3  4Relation::enforceMorphMap([ 5 'post' => 'App\Models\Post', 6 'video' => 'App\Models\Video', 7]); 8  9Feature::useMorphMap(); use Illuminate\Database\Eloquent\Relations\Relation; use Laravel\Pennant\Feature; Relation::enforceMorphMap([ 'post' => 'App\Models\Post', 'video' => 'App\Models\Video', ]); Feature::useMorphMap(); ## Rich Feature Values Until now, we have primarily shown features as being in a binary state, meaning they are either "active" or "inactive", but Pennant also allows you to store rich values as well. For example, imagine you are testing three new colors for the "Buy now" button of your application. Instead of returning `true` or `false` from the feature definition, you may instead return a string: 1use Illuminate\Support\Arr; 2use Laravel\Pennant\Feature; 3  4Feature::define('purchase-button', fn (User $user) => Arr::random([ 5 'blue-sapphire', 6 'seafoam-green', 7 'tart-orange', 8])); use Illuminate\Support\Arr; use Laravel\Pennant\Feature; Feature::define('purchase-button', fn (User $user) => Arr::random([ 'blue-sapphire', 'seafoam-green', 'tart-orange', ])); You may retrieve the value of the `purchase-button` feature using the `value` method: 1$color = Feature::value('purchase-button'); $color = Feature::value('purchase-button'); Pennant's included Blade directive also makes it easy to conditionally render content based on the current value of the feature: 1@feature('purchase-button', 'blue-sapphire') 2 3@elsefeature('purchase-button', 'seafoam-green') 4 5@elsefeature('purchase-button', 'tart-orange') 6 7@endfeature @feature('purchase-button', 'blue-sapphire') @elsefeature('purchase-button', 'seafoam-green') @elsefeature('purchase-button', 'tart-orange') @endfeature When using rich values, it is important to know that a feature is considered "active" when it has any value other than `false`. When calling the conditional `when` method, the feature's rich value will be provided to the first closure: 1Feature::when('purchase-button', 2 fn ($color) => /* ... */, 3 fn () => /* ... */, 4); Feature::when('purchase-button', fn ($color) => /* ... */, fn () => /* ... */, ); Likewise, when calling the conditional `unless` method, the feature's rich value will be provided to the optional second closure: 1Feature::unless('purchase-button', 2 fn () => /* ... */, 3 fn ($color) => /* ... */, 4); Feature::unless('purchase-button', fn () => /* ... */, fn ($color) => /* ... */, ); ## Retrieving Multiple Features The `values` method allows the retrieval of multiple features for a given scope: 1Feature::values(['billing-v2', 'purchase-button']); 2  3// [ 4// 'billing-v2' => false, 5// 'purchase-button' => 'blue-sapphire', 6// ] Feature::values(['billing-v2', 'purchase-button']); // [ // 'billing-v2' => false, // 'purchase-button' => 'blue-sapphire', // ] Or, you may use the `all` method to retrieve the values of all defined features for a given scope: 1Feature::all(); 2  3// [ 4// 'billing-v2' => false, 5// 'purchase-button' => 'blue-sapphire', 6// 'site-redesign' => true, 7// ] Feature::all(); // [ // 'billing-v2' => false, // 'purchase-button' => 'blue-sapphire', // 'site-redesign' => true, // ] However, class-based features are dynamically registered and are not known by Pennant until they are explicitly checked. This means your application's class-based features may not appear in the results returned by the `all` method if they have not already been checked during the current request. If you would like to ensure that feature classes are always included when using the `all` method, you may use Pennant's feature discovery capabilities. To get started, invoke the `discover` method in one of your application's service providers: 1 true, 5// 'billing-v2' => false, 6// 'purchase-button' => 'blue-sapphire', 7// 'site-redesign' => true, 8// ] Feature::all(); // [ // 'App\Features\NewApi' => true, // 'billing-v2' => false, // 'purchase-button' => 'blue-sapphire', // 'site-redesign' => true, // ] ## Eager Loading Although Pennant keeps an in-memory cache of all resolved features for a single request, it is still possible to encounter performance issues. To alleviate this, Pennant offers the ability to eager load feature values. To illustrate this, imagine that we are checking if a feature is active within a loop: 1use Laravel\Pennant\Feature; 2  3foreach ($users as $user) { 4 if (Feature::for($user)->active('notifications-beta')) { 5 $user->notify(new RegistrationSuccess); 6 } 7} use Laravel\Pennant\Feature; foreach ($users as $user) { if (Feature::for($user)->active('notifications-beta')) { $user->notify(new RegistrationSuccess); } } Assuming we are using the database driver, this code will execute a database query for every user in the loop - executing potentially hundreds of queries. However, using Pennant's `load` method, we can remove this potential performance bottleneck by eager loading the feature values for a collection of users or scopes: 1Feature::for($users)->load(['notifications-beta']); 2  3foreach ($users as $user) { 4 if (Feature::for($user)->active('notifications-beta')) { 5 $user->notify(new RegistrationSuccess); 6 } 7} Feature::for($users)->load(['notifications-beta']); foreach ($users as $user) { if (Feature::for($user)->active('notifications-beta')) { $user->notify(new RegistrationSuccess); } } To load feature values only when they have not already been loaded, you may use the `loadMissing` method: 1Feature::for($users)->loadMissing([ 2 'new-api', 3 'purchase-button', 4 'notifications-beta', 5]); Feature::for($users)->loadMissing([ 'new-api', 'purchase-button', 'notifications-beta', ]); You may load all defined features using the `loadAll` method: 1Feature::for($users)->loadAll(); Feature::for($users)->loadAll(); ## Updating Values When a feature's value is resolved for the first time, the underlying driver will store the result in storage. This is often necessary to ensure a consistent experience for your users across requests. However, at times, you may want to manually update the feature's stored value. To accomplish this, you may use the `activate` and `deactivate` methods to toggle a feature "on" or "off": 1use Laravel\Pennant\Feature; 2  3// Activate the feature for the default scope... 4Feature::activate('new-api'); 5  6// Deactivate the feature for the given scope... 7Feature::for($user->team)->deactivate('billing-v2'); use Laravel\Pennant\Feature; // Activate the feature for the default scope... Feature::activate('new-api'); // Deactivate the feature for the given scope... Feature::for($user->team)->deactivate('billing-v2'); It is also possible to manually set a rich value for a feature by providing a second argument to the `activate` method: 1Feature::activate('purchase-button', 'seafoam-green'); Feature::activate('purchase-button', 'seafoam-green'); To instruct Pennant to forget the stored value for a feature, you may use the `forget` method. When the feature is checked again, Pennant will resolve the feature's value from its feature definition: 1Feature::forget('purchase-button'); Feature::forget('purchase-button'); ### Bulk Updates To update stored feature values in bulk, you may use the `activateForEveryone` and `deactivateForEveryone` methods. For example, imagine you are now confident in the `new-api` feature's stability and have landed on the best `'purchase-button'` color for your checkout flow - you can update the stored value for all users accordingly: 1use Laravel\Pennant\Feature; 2  3Feature::activateForEveryone('new-api'); 4  5Feature::activateForEveryone('purchase-button', 'seafoam-green'); use Laravel\Pennant\Feature; Feature::activateForEveryone('new-api'); Feature::activateForEveryone('purchase-button', 'seafoam-green'); Alternatively, you may deactivate the feature for all users: 1Feature::deactivateForEveryone('new-api'); Feature::deactivateForEveryone('new-api'); This will only update the resolved feature values that have been stored by Pennant's storage driver. You will also need to update the feature definition in your application. ### Purging Features Sometimes, it can be useful to purge an entire feature from storage. This is typically necessary if you have removed the feature from your application or you have made adjustments to the feature's definition that you would like to rollout to all users. You may remove all stored values for a feature using the `purge` method: 1// Purging a single feature... 2Feature::purge('new-api'); 3  4// Purging multiple features... 5Feature::purge(['new-api', 'purchase-button']); // Purging a single feature... Feature::purge('new-api'); // Purging multiple features... Feature::purge(['new-api', 'purchase-button']); If you would like to purge _all_ features from storage, you may invoke the `purge` method without any arguments: 1Feature::purge(); Feature::purge(); As it can be useful to purge features as part of your application's deployment pipeline, Pennant includes a `pennant:purge` Artisan command which will purge the provided features from storage: 1php artisan pennant:purge new-api 2  3php artisan pennant:purge new-api purchase-button php artisan pennant:purge new-api php artisan pennant:purge new-api purchase-button It is also possible to purge all features _except_ those in a given feature list. For example, imagine you wanted to purge all features but keep the values for the "new-api" and "purchase-button" features in storage. To accomplish this, you can pass those feature names to the `--except` option: 1php artisan pennant:purge --except=new-api --except=purchase-button php artisan pennant:purge --except=new-api --except=purchase-button For convenience, the `pennant:purge` command also supports an `--except- registered` flag. This flag indicates that all features except those explicitly registered in a service provider should be purged: 1php artisan pennant:purge --except-registered php artisan pennant:purge --except-registered ## Testing When testing code that interacts with feature flags, the easiest way to control the feature flag's returned value in your tests is to simply re-define the feature. For example, imagine you have the following feature defined in one of your application's service provider: 1use Illuminate\Support\Arr; 2use Laravel\Pennant\Feature; 3  4Feature::define('purchase-button', fn () => Arr::random([ 5 'blue-sapphire', 6 'seafoam-green', 7 'tart-orange', 8])); use Illuminate\Support\Arr; use Laravel\Pennant\Feature; Feature::define('purchase-button', fn () => Arr::random([ 'blue-sapphire', 'seafoam-green', 'tart-orange', ])); To modify the feature's returned value in your tests, you may re-define the feature at the beginning of the test. The following test will always pass, even though the `Arr::random()` implementation is still present in the service provider: Pest PHPUnit 1use Laravel\Pennant\Feature; 2  3test('it can control feature values', function () { 4 Feature::define('purchase-button', 'seafoam-green'); 5  6 expect(Feature::value('purchase-button'))->toBe('seafoam-green'); 7}); use Laravel\Pennant\Feature; test('it can control feature values', function () { Feature::define('purchase-button', 'seafoam-green'); expect(Feature::value('purchase-button'))->toBe('seafoam-green'); }); 1use Laravel\Pennant\Feature; 2  3public function test_it_can_control_feature_values() 4{ 5 Feature::define('purchase-button', 'seafoam-green'); 6  7 $this->assertSame('seafoam-green', Feature::value('purchase-button')); 8} use Laravel\Pennant\Feature; public function test_it_can_control_feature_values() { Feature::define('purchase-button', 'seafoam-green'); $this->assertSame('seafoam-green', Feature::value('purchase-button')); } The same approach may be used for class-based features: Pest PHPUnit 1use Laravel\Pennant\Feature; 2  3test('it can control feature values', function () { 4 Feature::define(NewApi::class, true); 5  6 expect(Feature::value(NewApi::class))->toBeTrue(); 7}); use Laravel\Pennant\Feature; test('it can control feature values', function () { Feature::define(NewApi::class, true); expect(Feature::value(NewApi::class))->toBeTrue(); }); 1use App\Features\NewApi; 2use Laravel\Pennant\Feature; 3  4public function test_it_can_control_feature_values() 5{ 6 Feature::define(NewApi::class, true); 7  8 $this->assertTrue(Feature::value(NewApi::class)); 9} use App\Features\NewApi; use Laravel\Pennant\Feature; public function test_it_can_control_feature_values() { Feature::define(NewApi::class, true); $this->assertTrue(Feature::value(NewApi::class)); } If your feature is returning a `Lottery` instance, there are a handful of useful [testing helpers available](/docs/12.x/helpers#testing-lotteries). #### Store Configuration You may configure the store that Pennant will use during testing by defining the `PENNANT_STORE` environment variable in your application's `phpunit.xml` file: 1 2 3 4 5 6 7 8 ## Adding Custom Pennant Drivers #### Implementing the Driver If none of Pennant's existing storage drivers fit your application's needs, you may write your own storage driver. Your custom driver should implement the `Laravel\Pennant\Contracts\Driver` interface: 1make('redis'), $app->make('events'), []); 27 }); 28 } 29} make('redis'), $app->make('events'), []); }); } } Once the driver has been registered, you may use the `redis` driver in your application's `config/pennant.php` configuration file: 1'stores' => [ 2  3 'redis' => [ 4 'driver' => 'redis', 5 'connection' => null, 6 ], 7  8 // ... 9  10], 'stores' => [ 'redis' => [ 'driver' => 'redis', 'connection' => null, ], // ... ], ### Defining Features Externally If your driver is a wrapper around a third-party feature flag platform, you will likely define features on the platform rather than using Pennant's `Feature::define` method. If that is the case, your custom driver should also implement the `Laravel\Pennant\Contracts\DefinesFeaturesExternally` interface: 1feature}]."); 19 }); 20 } 21} feature}]."); }); } } ### `Laravel\Pennant\Events\DynamicallyRegisteringFeatureClass` This event is dispatched when a class-based feature is dynamically checked for the first time during a request. ### `Laravel\Pennant\Events\UnexpectedNullScopeEncountered` This event is dispatched when a `null` scope is passed to a feature definition that doesn't support null. This situation is handled gracefully and the feature will return `false`. However, if you would like to opt out of this feature's default graceful behavior, you may register a listener for this event in the `boot` method of your application's `AppServiceProvider`: 1use Illuminate\Support\Facades\Log; 2use Laravel\Pennant\Events\UnexpectedNullScopeEncountered; 3  4/** 5 * Bootstrap any application services. 6 */ 7public function boot(): void 8{ 9 Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500)); 10} use Illuminate\Support\Facades\Log; use Laravel\Pennant\Events\UnexpectedNullScopeEncountered; /** * Bootstrap any application services. */ public function boot(): void { Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500)); } ### `Laravel\Pennant\Events\FeatureUpdated` This event is dispatched when updating a feature for a scope, usually by calling `activate` or `deactivate`. ### `Laravel\Pennant\Events\FeatureUpdatedForAllScopes` This event is dispatched when updating a feature for all scopes, usually by calling `activateForEveryone` or `deactivateForEveryone`. ### `Laravel\Pennant\Events\FeatureDeleted` This event is dispatched when deleting a feature for a scope, usually by calling `forget`. ### `Laravel\Pennant\Events\FeaturesPurged` This event is dispatched when purging specific features. ### `Laravel\Pennant\Events\AllFeaturesPurged` This event is dispatched when purging all features.