> ## 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

> Uitleg over het beheren van feature flags met Laravel Pennant. Behandelt het definiëren en controleren van features, scopes en testen.

## Wat is Laravel Pennant

[Laravel Pennant](https://github.com/laravel/pennant) is een eenvoudig en lichtgewicht pakket voor feature flags. Met feature flags kun je nieuwe functionaliteit stapsgewijs uitrollen, A/B-tests uitvoeren en trunk-based development ondersteunen.

### Wat zijn feature flags

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

Met feature flags kun je het deployen van code loskoppelen van het releasen. Je kunt de code naar productie deployen en het aan- of uitzetten van functionaliteit via configuratie regelen.

***

## Installatie

<Steps>
  <Step title="Het pakket installeren">
    Installeer Pennant met Composer.

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

  <Step title="Configuratiebestand en migraties publiceren">
    Publiceer de bestanden met het Artisan-commando `vendor:publish`.

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

    Hiermee worden `config/pennant.php` en de migratiebestanden in `database/migrations` aangemaakt.
  </Step>

  <Step title="De migraties uitvoeren">
    Maakt de tabel `features` aan waarin Pennant de waarden van feature flags opslaat.

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

***

## Configuratie

In `config/pennant.php` stel je de te gebruiken storagedriver in. Pennant ondersteunt twee drivers.

| Driver     | Beschrijving                                                       |
| ---------- | ------------------------------------------------------------------ |
| `database` | Slaat waarden permanent op in een relationele database (standaard) |
| `array`    | Slaat op in het geheugen (voor tests of tijdelijk gebruik)         |

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

***

## Features definiëren

### Definitie op basis van closures

Features definieer je met de `define`-methode van de `Feature`-facade. Meestal doe je dit in de `boot`-methode van een serviceprovider. Aan de closure wordt een "scope" doorgegeven (meestal de geauthenticeerde gebruiker).

```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),
        });
    }
}
```

De logica van deze feature is als volgt:

* Interne teamleden staan altijd op AAN
* Klanten met veel verkeer staan op UIT
* Voor de rest is de kans 1% dat de feature AAN staat

Wanneer een feature voor het eerst wordt gecontroleerd, wordt het resultaat van de closure opgeslagen in de storagedriver. Bij volgende keren wordt de opgeslagen waarde gebruikt.

<Info>
  Als de definitie alleen een Lottery teruggeeft, kun je de closure weglaten.

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

### Definitie op basis van klassen

Pennant ondersteunt ook feature-definities op basis van klassen. Bij klassen is registratie in een serviceprovider niet nodig.

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

De gegenereerde klasse wordt in de directory `app/Features` geplaatst. Je hoeft alleen de `resolve`-methode te implementeren.

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

namespace App\Features;

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

class NewApi
{
    /**
     * Bepaal de initiële waarde van de feature
     */
    public function resolve(User $user): mixed
    {
        return match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        };
    }
}
```

#### De opgeslagen naam aanpassen

Standaard wordt de volledig gekwalificeerde klassenaam opgeslagen. Met het `Name`-attribuut kun je de naam aanpassen.

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

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

#### Featurechecks onderscheppen (de `before`-methode)

Aan een klassegebaseerde feature kun je een `before`-methode toevoegen. Deze methode wordt in het geheugen uitgevoerd vóórdat de waarde uit de storage wordt opgehaald; geeft hij een waarde anders dan `null` terug, dan wordt die waarde gebruikt.

```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>
  De `before`-methode is handig om een feature in noodgevallen uit te schakelen bij een bug, of om een uitrol op een bepaald tijdstip in te plannen.
</Tip>

***

## Features controleren

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

Met de `active`-methode controleer je of een feature actief is. Standaard wordt de check uitgevoerd voor de momenteel geauthenticeerde gebruiker.

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

if (Feature::active('new-api')) {
    // Verwerking met de nieuwe API
}
```

Bij een klassegebaseerde feature geef je de klassenaam door.

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

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

Er zijn ook andere handige methodes beschikbaar.

```php theme={null}
// Zijn alle features actief
Feature::allAreActive(['new-api', 'site-redesign']);

// Is minstens één feature actief
Feature::someAreActive(['new-api', 'site-redesign']);

// Is de feature inactief
Feature::inactive('new-api');

// Zijn alle features inactief
Feature::allAreInactive(['new-api', 'site-redesign']);

// Is minstens één feature inactief
Feature::someAreInactive(['new-api', 'site-redesign']);
```

### Voorwaardelijk uitvoeren (`when` / `unless`)

Met de `when`-methode voer je een closure alleen uit als de feature actief is.

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

`unless` is het omgekeerde van `when` en voert de eerste closure uit als de feature inactief is.

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

### De `HasFeatures`-trait

Voeg je de `HasFeatures`-trait toe aan het `User`-model, dan kun je features rechtstreeks vanuit het model controleren.

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

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

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

// De waarde ophalen
$value = $user->features()->value('purchase-button');

// Voorwaardelijk uitvoeren
$user->features()->when('new-api',
    fn () => /* ... */,
    fn () => /* ... */,
);
```

### Blade-directives

In Blade-templates kun je de `@feature`-directive gebruiken.

```blade theme={null}
@feature('site-redesign')
    {{-- Als 'site-redesign' actief is --}}
@else
    {{-- Als 'site-redesign' inactief is --}}
@endfeature

@featureany(['site-redesign', 'beta'])
    {{-- Als een van beide actief is --}}
@endfeatureany
```

### Middleware

Met de `EnsureFeaturesAreActive`-middleware geef je aan dat toegang tot een route een feature vereist. Is de feature inactief, dan wordt een `400 Bad Request` teruggegeven.

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

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

Gebruik de `whenInactive`-methode om de response aan te passen.

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

### In-memory cache

Pennant cachet featureresultaten in het geheugen binnen één request. Zelfs als je dezelfde feature flag meerdere keren controleert, worden er geen extra databasequery's uitgevoerd.

Gebruik de `flushCache`-methode om de cache handmatig te legen.

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

***

## Scopes

### Een scope opgeven

Standaard is de geauthenticeerde gebruiker de scope, maar met de `for`-methode kun je een willekeurige scope opgeven.

```php theme={null}
// Controleren voor een specifieke gebruiker
Feature::for($user)->active('new-api');

// Controleren voor een team
Feature::for($user->team)->active('billing-v2');
```

Een voorbeeld waarbij features per team worden beheerd.

```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);
});
```

### De standaardscope aanpassen

Met `Feature::resolveScopeUsing` kun je de standaardscope aanpassen.

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

Na configuratie wordt bij het weglaten van `for` de standaardscope gebruikt.

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

### Nullable scope

Als de scope `null` is (niet-geauthenticeerde routes, Artisan-commando's, enzovoort) en de feature-definitie niet met null overweg kan, wordt automatisch `false` teruggegeven. Wil je null verwerken, definieer dan met een nullable type.

```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),
});
```

***

## Rijke featurewaarden

Features kunnen ook andere waarden dan booleans teruggeven. Bijvoorbeeld om de kleur van een knop te bepalen in een A/B-test.

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

Gebruik de `value`-methode om de waarde op te halen.

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

In Blade kun je ook op waarde vertakken.

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

<Info>
  Bij rijke waarden wordt elke waarde behalve `false` als actief beschouwd.
</Info>

Wanneer een rijke waarde aan de `when`-methode wordt doorgegeven, ontvangt de eerste closure de waarde.

```php theme={null}
Feature::when('purchase-button',
    fn ($color) => /* $color bevat de waarde */,
    fn () => /* wanneer inactief */,
);
```

***

## Meerdere features ophalen

Met de `values`-methode haal je de waarden van meerdere features in één keer op.

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

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

Met de `all`-methode haal je de waarden van alle gedefinieerde features op.

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

Om klassegebaseerde features in het resultaat van `all` op te nemen, roep je `discover` aan in een serviceprovider.

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

Hiermee worden alle featureklassen in de directory `app/Features` geregistreerd.

***

## Eager loading

Wanneer je featurechecks in een lus uitvoert, kunnen prestatieproblemen ontstaan. Dit los je op door de waarden vooraf op te halen met de `load`-methode.

```php theme={null}
// Fout: per iteratie een databasequery
foreach ($users as $user) {
    if (Feature::for($user)->active('notifications-beta')) {
        $user->notify(new RegistrationSuccess);
    }
}

// Goed: vooraf in één keer ophalen
Feature::for($users)->load(['notifications-beta']);

foreach ($users as $user) {
    if (Feature::for($user)->active('notifications-beta')) {
        $user->notify(new RegistrationSuccess);
    }
}
```

Gebruik `loadMissing` om alleen nog niet opgehaalde waarden op te halen.

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

***

## Waarden bijwerken

### Handmatig bijwerken

Met de methodes `activate` / `deactivate` schakel je een feature aan of uit.

```php theme={null}
// Activeren voor de standaardscope
Feature::activate('new-api');

// Deactiveren voor een specifieke scope
Feature::for($user->team)->deactivate('billing-v2');

// Een rijke waarde instellen
Feature::activate('purchase-button', 'seafoam-green');
```

Gebruik de `forget`-methode om de opgeslagen waarde te laten vergeten. Bij de volgende check wordt de waarde opnieuw geëvalueerd op basis van de definitie.

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

### Bulksgewijs bijwerken

Met `activateForEveryone` / `deactivateForEveryone` pas je een waarde in één keer toe op alle scopes in de storage.

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

### Features purgen

Als je een feature uit je applicatie verwijdert of de definitie wijzigt, kun je de waarden uit de storage verwijderen (purgen).

```php theme={null}
// Eén feature purgen
Feature::purge('new-api');

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

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

Purgen kan ook met een Artisan-commando. Handig om op te nemen in je deploypipeline.

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

# Meerdere opgeven
php artisan pennant:purge new-api purchase-button

# Alles purgen behalve de opgegeven features
php artisan pennant:purge --except=new-api --except=purchase-button

# Alles purgen behalve de in serviceproviders geregistreerde features
php artisan pennant:purge --except-registered
```

***

## Testen

### Features herdefiniëren

In tests kun je de returnwaarde sturen door de feature opnieuw te definiëren met `Feature::define`.

```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'));
}
```

Klassegebaseerde features werken op dezelfde manier.

```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));
}
```

### De store voor tests instellen

De store die tijdens tests wordt gebruikt, kun je opgeven via een omgevingsvariabele in `phpunit.xml`.

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

***

## Custom drivers

Als de bestaande drivers niet aan je eisen voldoen, kun je een custom driver maken. Je implementeert de interface `Laravel\Pennant\Contracts\Driver`.

```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 {}
}
```

Registreer de driver door `extend` aan te roepen in de `boot`-methode van een serviceprovider.

```php theme={null}
Feature::extend('redis', function (Application $app) {
    return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);
});
```

Na registratie kun je de driver opgeven in `config/pennant.php`.

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

***

## Samenvatting

| Wat je wilt doen         | Methode                                           |
| ------------------------ | ------------------------------------------------- |
| Pennant installeren      | `composer require laravel/pennant`                |
| Een feature definiëren   | `Feature::define('name', fn ($user) => ...)`      |
| Een feature controleren  | `Feature::active('name')`                         |
| Controleren in Blade     | `@feature('name') ... @endfeature`                |
| Een waarde bijwerken     | `Feature::activate('name')` / `deactivate`        |
| Toepassen op alle scopes | `Feature::activateForEveryone('name')`            |
| Sturen in tests          | Herdefiniëren met `Feature::define('name', true)` |
| Purgen uit de storage    | `Feature::purge('name')`                          |

## Volgende stappen

<Columns cols={2}>
  <Card title="Debuggen en foutafhandeling" icon="circle-x" href="/nl/error-handling">
    Leer hoe exceptions en rapportage in je applicatie werken.
  </Card>

  <Card title="Laravel Pulse" icon="chart-line" href="/nl/pulse">
    Zet een dashboard op voor het monitoren van de prestaties van je applicatie.
  </Card>
</Columns>


## Related topics

- [Praktische use-cases voor Laravel Pennant](/nl/blog/laravel-pennant.md)
- [部落格](/zh-TW/blog/index.md)
- [博客](/zh-CN/blog/index.md)
- [Laravel Boost](/nl/boost.md)
- [Laravel Telescope](/nl/telescope.md)
