> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel Pennant

> Verwalten Sie Feature Flags mit Laravel Pennant. Von der Feature-Definition über die Prüfung, Scopes und Tests bis zu eigenen Treibern.

## Was ist Laravel Pennant

[Laravel Pennant](https://github.com/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.

### Was sind Feature Flags

```mermaid theme={null}
flowchart TD
    A["Request"] --> B{"Feature::active('new-api')"}
    B -->|true| C["Neue API-Verarbeitung"]
    B -->|false| D["Bisherige API-Verarbeitung"]
    C --> E["Response"]
    D --> E
```

Mit Feature Flags entkoppeln Sie Deployment und Release. Sie können den Code in Produktion deployen und das Feature konfigurativ ein- oder ausschalten.

***

## Installation

<Steps>
  <Step title="Paket installieren">
    Installieren Sie Pennant mit Composer.

    ```bash theme={null}
    composer require laravel/pennant
    ```
  </Step>

  <Step title="Konfigurationsdatei und Migrationen veröffentlichen">
    Veröffentlichen Sie die Dateien mit dem Artisan-Befehl `vendor:publish`.

    ```bash theme={null}
    php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
    ```

    Dadurch entstehen `config/pennant.php` und Migrationen unter `database/migrations`.
  </Step>

  <Step title="Migrationen ausführen">
    Legt die Tabelle `features` an, in der Pennant die Werte der Feature Flags speichert.

    ```bash theme={null}
    php artisan migrate
    ```
  </Step>
</Steps>

***

## Konfiguration

In `config/pennant.php` legen Sie den verwendeten Storage-Treiber fest. Pennant unterstützt zwei Treiber:

| Treiber    | Beschreibung                                                       |
| ---------- | ------------------------------------------------------------------ |
| `database` | Persistente Speicherung in einer relationalen Datenbank (Standard) |
| `array`    | In-Memory-Speicherung (für Tests oder temporäre Nutzung)           |

```php theme={null}
// config/pennant.php
'default' => env('PENNANT_STORE', 'database'),
```

***

## Features definieren

### Definition per Closure

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

```php theme={null}
<?php

namespace App\Providers;

use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Feature::define('new-api', fn (User $user) => match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        });
    }
}
```

Die Logik lautet:

* Interne Teammitglieder erhalten das Feature immer.
* Kunden mit hohem Traffic erhalten es nicht.
* Für alle anderen wird es mit 1 % Wahrscheinlichkeit aktiviert.

Beim ersten Prüfen wird das Ergebnis der Closure im Storage-Treiber gespeichert. Danach wird der gespeicherte Wert verwendet.

<Info>
  Wenn die Definition nur eine Lottery zurückgibt, können Sie die Closure weglassen.

  ```php theme={null}
  Feature::define('site-redesign', Lottery::odds(1, 1000));
  ```
</Info>

### Definition als Klasse

Pennant unterstützt auch klassenbasierte Feature-Definitionen. In diesem Fall ist keine Registrierung im Service-Provider erforderlich.

```bash theme={null}
php artisan pennant:feature NewApi
```

Die generierte Klasse liegt in `app/Features`. Implementieren Sie die Methode `resolve`.

```php theme={null}
<?php

namespace App\Features;

use App\Models\User;
use Illuminate\Support\Lottery;

class NewApi
{
    /**
     * Löst den initialen Wert des Features auf.
     */
    public function resolve(User $user): mixed
    {
        return match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        };
    }
}
```

#### Speichernamen anpassen

Standardmäßig wird der voll qualifizierte Klassenname gespeichert. Mit dem Attribut `Name` können Sie den Namen anpassen.

```php theme={null}
use Laravel\Pennant\Attributes\Name;

#[Name('new-api')]
class NewApi
{
    // ...
}
```

#### Feature-Prüfung abfangen (Methode `before`)

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.

```php theme={null}
class NewApi
{
    public function before(User $user): mixed
    {
        if (Config::get('features.new-api.disabled')) {
            return $user->isInternalTeamMember();
        }
    }

    public function resolve(User $user): mixed
    {
        // ...
    }
}
```

<Tip>
  Die `before`-Methode ist hilfreich, um bei Fehlern das Feature notfallmäßig zu deaktivieren oder einen Rollout auf einen bestimmten Zeitpunkt zu terminieren.
</Tip>

***

## Features prüfen

### `Feature::active()` / `Feature::inactive()`

Mit `active` prüfen Sie, ob ein Feature aktiv ist. Standardmäßig wird der aktuell authentifizierte Benutzer verwendet.

```php theme={null}
use Laravel\Pennant\Feature;

if (Feature::active('new-api')) {
    // Die neue API verwenden
}
```

Für klassenbasierte Features übergeben Sie den Klassennamen.

```php theme={null}
use App\Features\NewApi;

if (Feature::active(NewApi::class)) {
    // ...
}
```

Weitere praktische Methoden:

```php theme={null}
// 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']);
```

### Bedingte Ausführung (`when` / `unless`)

Mit `when` führen Sie eine Closure nur aus, wenn das Feature aktiv ist.

```php theme={null}
return Feature::when(NewApi::class,
    fn () => $this->resolveNewApiResponse($request),
    fn () => $this->resolveLegacyApiResponse($request),
);
```

`unless` ist die Umkehrung von `when`; die erste Closure läuft, wenn das Feature inaktiv ist.

```php theme={null}
return Feature::unless(NewApi::class,
    fn () => $this->resolveLegacyApiResponse($request),
    fn () => $this->resolveNewApiResponse($request),
);
```

### Trait `HasFeatures`

Fügen Sie dem `User`-Modell das Trait `HasFeatures` hinzu, können Sie Features direkt am Modell prüfen.

```php theme={null}
use Laravel\Pennant\Concerns\HasFeatures;

class User extends Authenticatable
{
    use HasFeatures;
}
```

```php theme={null}
if ($user->features()->active('new-api')) {
    // ...
}

// Wert abrufen
$value = $user->features()->value('purchase-button');

// Bedingte Ausführung
$user->features()->when('new-api',
    fn () => /* ... */,
    fn () => /* ... */,
);
```

### Blade-Direktiven

In Blade können Sie die Direktive `@feature` verwenden.

```blade theme={null}
@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
```

### Middleware

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.

```php theme={null}
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;

Route::get('/api/servers', function () {
    // ...
})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api'));
```

Über `whenInactive` passen Sie die Antwort an.

```php theme={null}
EnsureFeaturesAreActive::whenInactive(
    function (Request $request, array $features) {
        return new Response(status: 403);
    }
);
```

### In-Memory-Cache

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

```php theme={null}
Feature::flushCache();
```

***

## Scopes

### Scope festlegen

Standardmäßig ist der authentifizierte Benutzer der Scope. Mit `for` können Sie einen beliebigen Scope angeben.

```php theme={null}
// Für einen bestimmten Benutzer prüfen
Feature::for($user)->active('new-api');

// Für ein Team prüfen
Feature::for($user->team)->active('billing-v2');
```

Ein Beispiel, das Features je Team steuert:

```php theme={null}
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);
});
```

### Standard-Scope anpassen

Mit `Feature::resolveScopeUsing` passen Sie den Standard-Scope an.

```php theme={null}
Feature::resolveScopeUsing(fn ($driver) => Auth::user()?->team);
```

Danach wird ohne `for` der Standard-Scope verwendet.

```php theme={null}
Feature::active('billing-v2');
// entspricht:
Feature::for($user->team)->active('billing-v2');
```

### Nullable Scope

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.

```php theme={null}
Feature::define('new-api', fn (User|null $user) => match (true) {
    $user === null => true,
    $user->isInternalTeamMember() => true,
    $user->isHighTrafficCustomer() => false,
    default => Lottery::odds(1 / 100),
});
```

***

## Rich Feature Values

Features können auch andere Werte als Booleans zurückgeben – etwa um in einem A/B-Test die Buttonfarbe zu steuern.

```php theme={null}
Feature::define('purchase-button', fn (User $user) => Arr::random([
    'blue-sapphire',
    'seafoam-green',
    'tart-orange',
]));
```

Den Wert erhalten Sie mit `value`.

```php theme={null}
$color = Feature::value('purchase-button');
```

In Blade sind auch werteabhängige Verzweigungen möglich.

```blade theme={null}
@feature('purchase-button', 'blue-sapphire')
    {{-- blue-sapphire aktiv --}}
@elsefeature('purchase-button', 'seafoam-green')
    {{-- seafoam-green aktiv --}}
@elsefeature('purchase-button', 'tart-orange')
    {{-- tart-orange aktiv --}}
@endfeature
```

<Info>
  Bei Rich Values gilt jeder Wert außer `false` als aktiv.
</Info>

Übergibt `when` einen Rich Value, erhält die erste Closure diesen Wert.

```php theme={null}
Feature::when('purchase-button',
    fn ($color) => /* $color enthält den Wert */,
    fn () => /* wenn inaktiv */,
);
```

***

## Mehrere Features gleichzeitig abrufen

Mit `values` holen Sie mehrere Feature-Werte auf einmal.

```php theme={null}
Feature::values(['billing-v2', 'purchase-button']);

// [
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
// ]
```

Mit `all` erhalten Sie alle definierten Feature-Werte.

```php theme={null}
Feature::all();
```

Um klassenbasierte Features in `all` einzubeziehen, rufen Sie im Service-Provider `discover` auf.

```php theme={null}
Feature::discover();
```

Damit werden alle Feature-Klassen im Verzeichnis `app/Features` registriert.

***

## Eager Loading

Prüfungen in Schleifen können zu Performance-Problemen führen. Mit `load` laden Sie die Werte vorab.

```php theme={null}
// Schlecht: pro Schleifendurchlauf eine DB-Query
foreach ($users as $user) {
    if (Feature::for($user)->active('notifications-beta')) {
        $user->notify(new RegistrationSuccess);
    }
}

// Gut: vorab in einem Rutsch laden
Feature::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`.

```php theme={null}
Feature::for($users)->loadMissing([
    'new-api',
    'purchase-button',
    'notifications-beta',
]);
```

***

## Werte aktualisieren

### Manuell

Mit `activate` und `deactivate` schalten Sie Features ein bzw. aus.

```php theme={null}
// Im Standard-Scope aktivieren
Feature::activate('new-api');

// In einem bestimmten Scope deaktivieren
Feature::for($user->team)->deactivate('billing-v2');

// Rich Value setzen
Feature::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.

```php theme={null}
Feature::forget('purchase-button');
```

### Sammel-Update

Mit `activateForEveryone` und `deactivateForEveryone` wenden Sie eine Änderung auf alle Scopes im Storage an.

```php theme={null}
Feature::activateForEveryone('new-api');
Feature::activateForEveryone('purchase-button', 'seafoam-green');
Feature::deactivateForEveryone('new-api');
```

### Features purgen

Wenn ein Feature aus der Anwendung entfernt oder seine Definition geändert wurde, können Sie die Werte aus dem Storage entfernen (purgen).

```php theme={null}
// Einzelnes Feature purgen
Feature::purge('new-api');

// Mehrere Features purgen
Feature::purge(['new-api', 'purchase-button']);

// Alle Features purgen
Feature::purge();
```

Es geht auch per Artisan-Befehl – ideal für die Deploy-Pipeline.

```bash theme={null}
php artisan pennant:purge new-api

# Mehrere angeben
php artisan pennant:purge new-api purchase-button

# Alle außer den angegebenen Features purgen
php artisan pennant:purge --except=new-api --except=purchase-button

# Alle purgen, die nicht im Service-Provider registriert sind
php artisan pennant:purge --except-registered
```

***

## Tests

### Features neu definieren

In Tests steuern Sie die Rückgabewerte, indem Sie das Feature per `Feature::define` neu definieren.

```php tab=Pest theme={null}
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');
});
```

```php tab=PHPUnit theme={null}
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.

```php tab=Pest theme={null}
test('it can control feature values', function () {
    Feature::define(NewApi::class, true);

    expect(Feature::value(NewApi::class))->toBeTrue();
});
```

```php tab=PHPUnit theme={null}
use App\Features\NewApi;

public function test_it_can_control_feature_values(): void
{
    Feature::define(NewApi::class, true);

    $this->assertTrue(Feature::value(NewApi::class));
}
```

### Test-Store konfigurieren

Über eine Umgebungsvariable in `phpunit.xml` legen Sie den während Tests verwendeten Store fest.

```xml theme={null}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
    <php>
        <env name="PENNANT_STORE" value="array"/>
    </php>
</phpunit>
```

***

## Eigene Treiber

Wenn die vorhandenen Treiber nicht ausreichen, können Sie einen eigenen Treiber erstellen und dabei das Interface `Laravel\Pennant\Contracts\Driver` implementieren.

```php theme={null}
<?php

namespace 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`.

```php theme={null}
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.

```php theme={null}
'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => null,
    ],
],
```

***

## Zusammenfassung

| Ziel                       | Vorgehen                                           |
| -------------------------- | -------------------------------------------------- |
| Feature Flags installieren | `composer require laravel/pennant`                 |
| Feature definieren         | `Feature::define('name', fn ($user) => ...)`       |
| Feature prüfen             | `Feature::active('name')`                          |
| In Blade prüfen            | `@feature('name') ... @endfeature`                 |
| Wert aktualisieren         | `Feature::activate('name')` / `deactivate`         |
| Auf alle Scopes anwenden   | `Feature::activateForEveryone('name')`             |
| In Tests steuern           | Mit `Feature::define('name', true)` neu definieren |
| Aus dem Storage entfernen  | `Feature::purge('name')`                           |

## Nächste Schritte

<Columns cols={2}>
  <Card title="Debugging und Error Handling" icon="circle-x" href="/de/error-handling">
    Lernen Sie, wie Ihre Anwendung Ausnahmen behandelt und meldet.
  </Card>

  <Card title="Laravel Pulse" icon="chart-line" href="/de/pulse">
    Führen Sie ein Performance-Monitoring-Dashboard für Ihre Anwendung ein.
  </Card>
</Columns>


## Related topics

- [Laravel Pennant – Praxis-Anwendungsfälle](/de/blog/laravel-pennant.md)
- [Laravel Boost](/de/boost.md)
- [Laravel-Paketentwicklung](/de/advanced/package-development.md)
- [Laravel Telescope](/de/telescope.md)
- [Laravel Bluesky](/de/packages/laravel-bluesky/index.md)
