Laravel Pennant ist ein schlankes, einfaches Paket für Feature Flags. Mit Feature Flags können Sie neue Funktionen schrittweise ausrollen, A/B-Tests durchführen oder Trunk-Based Development unterstützen.
Mit Feature Flags entkoppeln Sie Deployment und Release. Sie können den Code in Produktion deployen und das Feature konfigurativ ein- oder ausschalten.
Features werden über die Methode define der Feature-Fassade definiert – üblicherweise in der boot-Methode eines Service-Providers. Die Closure erhält einen „Scope“ (meist den authentifizierten Benutzer).
In klassenbasierten Features können Sie eine before-Methode definieren. Sie wird vor dem Zugriff auf den Storage im Speicher ausgeführt; gibt sie einen anderen Wert als null zurück, wird dieser verwendet.
class NewApi{ public function before(User $user): mixed { if (Config::get('features.new-api.disabled')) { return $user->isInternalTeamMember(); } } public function resolve(User $user): mixed { // ... }}
Die before-Methode ist hilfreich, um bei Fehlern das Feature notfallmäßig zu deaktivieren oder einen Rollout auf einen bestimmten Zeitpunkt zu terminieren.
Mit active prüfen Sie, ob ein Feature aktiv ist. Standardmäßig wird der aktuell authentifizierte Benutzer verwendet.
use Laravel\Pennant\Feature;if (Feature::active('new-api')) { // Die neue API verwenden}
Für klassenbasierte Features übergeben Sie den Klassennamen.
use App\Features\NewApi;if (Feature::active(NewApi::class)) { // ...}
Weitere praktische Methoden:
// Sind alle Features aktiv?Feature::allAreActive(['new-api', 'site-redesign']);// Ist eines der Features aktiv?Feature::someAreActive(['new-api', 'site-redesign']);// Ist das Feature inaktiv?Feature::inactive('new-api');// Sind alle Features inaktiv?Feature::allAreInactive(['new-api', 'site-redesign']);// Ist eines der Features inaktiv?Feature::someAreInactive(['new-api', 'site-redesign']);
In Blade können Sie die Direktive @feature verwenden.
@feature('site-redesign') {{-- Wenn 'site-redesign' aktiv ist --}}@else {{-- Wenn 'site-redesign' inaktiv ist --}}@endfeature@featureany(['site-redesign', 'beta']) {{-- Wenn eines der beiden aktiv ist --}}@endfeatureany
Mit der Middleware EnsureFeaturesAreActive geben Sie an, dass eine Route ein bestimmtes Feature benötigt. Ist das Feature inaktiv, wird 400 Bad Request zurückgegeben.
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;Route::get('/api/servers', function () { // ...})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api'));
Über whenInactive passen Sie die Antwort an.
EnsureFeaturesAreActive::whenInactive( function (Request $request, array $features) { return new Response(status: 403); });
Innerhalb eines Requests cached Pennant die Ergebnisse im Speicher. Mehrfache Prüfungen desselben Feature Flags erzeugen keine zusätzlichen DB-Queries.Zum manuellen Leeren des Caches verwenden Sie flushCache.
Ist der Scope null (z. B. bei nicht authentifizierten Routen oder Artisan-Befehlen) und die Feature-Definition kann kein null verarbeiten, wird automatisch false zurückgegeben. Wenn Sie null behandeln möchten, deklarieren Sie den Parameter als nullable.
Prüfungen in Schleifen können zu Performance-Problemen führen. Mit load laden Sie die Werte vorab.
// Schlecht: pro Schleifendurchlauf eine DB-Queryforeach ($users as $user) { if (Feature::for($user)->active('notifications-beta')) { $user->notify(new RegistrationSuccess); }}// Gut: vorab in einem Rutsch ladenFeature::for($users)->load(['notifications-beta']);foreach ($users as $user) { if (Feature::for($user)->active('notifications-beta')) { $user->notify(new RegistrationSuccess); }}
Um nur noch nicht geladene Werte zu laden, verwenden Sie loadMissing.
Mit activate und deactivate schalten Sie Features ein bzw. aus.
// Im Standard-Scope aktivierenFeature::activate('new-api');// In einem bestimmten Scope deaktivierenFeature::for($user->team)->deactivate('billing-v2');// Rich Value setzenFeature::activate('purchase-button', 'seafoam-green');
Um den gespeicherten Wert zu verwerfen, verwenden Sie forget. Bei der nächsten Prüfung wird die Definition neu ausgewertet.
Wenn ein Feature aus der Anwendung entfernt oder seine Definition geändert wurde, können Sie die Werte aus dem Storage entfernen (purgen).
// Einzelnes Feature purgenFeature::purge('new-api');// Mehrere Features purgenFeature::purge(['new-api', 'purchase-button']);// Alle Features purgenFeature::purge();
Es geht auch per Artisan-Befehl – ideal für die Deploy-Pipeline.
php artisan pennant:purge new-api# Mehrere angebenphp artisan pennant:purge new-api purchase-button# Alle außer den angegebenen Features purgenphp artisan pennant:purge --except=new-api --except=purchase-button# Alle purgen, die nicht im Service-Provider registriert sindphp artisan pennant:purge --except-registered
In Tests steuern Sie die Rückgabewerte, indem Sie das Feature per Feature::define neu definieren.
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'));}
Klassenbasierte Features funktionieren genauso.
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));}
Wenn die vorhandenen Treiber nicht ausreichen, können Sie einen eigenen Treiber erstellen und dabei das Interface Laravel\Pennant\Contracts\Driver implementieren.
<?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 {}}
Registrieren Sie den Treiber in der boot-Methode eines Service-Providers per extend.
Feature::extend('redis', function (Application $app) { return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);});
Anschließend können Sie den Treiber in config/pennant.php konfigurieren.