Laravel Pennant est un package léger et simple pour les feature flags. Les flags permettent un rollout progressif, des A/B tests et complètent le développement trunk-based.
Cette méthode s’exécute avant la lecture du stockage : si elle retourne autre chose que null, cette valeur est utilisée.
class NewApi{ public function before(User $user): mixed { if (Config::get('features.new-api.disabled')) { return $user->isInternalTeamMember(); } } public function resolve(User $user): mixed { // ... }}
Utile pour un kill switch en cas de bug ou un rollout programmé.
@feature('site-redesign') {{-- Si actif --}}@else {{-- Si inactif --}}@endfeature@featureany(['site-redesign', 'beta']) {{-- L'un ou l'autre actif --}}@endfeatureany
EnsureFeaturesAreActive protège une route ; sans le flag, 400 Bad Request.
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;Route::get('/api/servers', function () { // ...})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api'));
Personnalisez la réponse via whenInactive.
EnsureFeaturesAreActive::whenInactive( function (Request $request, array $features) { return new Response(status: 403); });
Sans authentification (Artisan, etc.), une définition non typée pour null retourne automatiquement false. Typez avec nullable si vous voulez le gérer :
Dans une boucle, les checks entraînent des requêtes. Chargez à l’avance avec load.
// KO : requête par itérationforeach ($users as $user) { if (Feature::for($user)->active('notifications-beta')) { $user->notify(new RegistrationSuccess); }}// OK : chargement en amontFeature::for($users)->load(['notifications-beta']);foreach ($users as $user) { if (Feature::for($user)->active('notifications-beta')) { $user->notify(new RegistrationSuccess); }}
php artisan pennant:purge new-api# Plusieursphp artisan pennant:purge new-api purchase-button# Purger tout sauf certainesphp artisan pennant:purge --except=new-api --except=purchase-button# Purger tout sauf ce qui est enregistré dans un providerphp artisan pennant:purge --except-registered
Dans un test, redéfinissez pour contrôler la valeur.
tab=Pest
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');});
tab=PHPUnit
use Laravel\Pennant\Feature;public function test_it_can_control_feature_values(): void{ Feature::define('purchase-button', 'seafoam-green'); $this->assertSame('seafoam-green', Feature::value('purchase-button'));}
Idem avec les classes.
tab=Pest
test('it can control feature values', function () { Feature::define(NewApi::class, true); expect(Feature::value(NewApi::class))->toBeTrue();});
tab=PHPUnit
use App\Features\NewApi;public function test_it_can_control_feature_values(): void{ Feature::define(NewApi::class, true); $this->assertTrue(Feature::value(NewApi::class));}
<?phpnamespace App\Extensions;use Laravel\Pennant\Contracts\Driver;class RedisFeatureDriver implements Driver{ public function define(string $feature, callable $resolver): void {} public function defined(): array {} public function getAll(array $features): array {} public function get(string $feature, mixed $scope): mixed {} public function set(string $feature, mixed $scope, mixed $value): void {} public function setForAllScopes(string $feature, mixed $value): void {} public function delete(string $feature, mixed $scope): void {} public function purge(array|null $features): void {}}
Enregistrez avec extend.
Feature::extend('redis', function (Application $app) { return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);});