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

> Gestione dei feature flag con Laravel Pennant: definizione, verifica, scope e test.

## Cos'è Laravel Pennant

[Laravel Pennant](https://github.com/laravel/pennant) è un pacchetto leggero per i feature flag. Ti permette rollout progressivi, A/B test e trunk-based development.

### Cosa sono i feature flag

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

I flag separano deploy e rilascio: il codice va in produzione ma la funzionalità è controllata da config.

***

## Installazione

<Steps>
  <Step title="Pacchetto">
    ```bash theme={null}
    composer require laravel/pennant
    ```
  </Step>

  <Step title="Pubblica config e migration">
    ```bash theme={null}
    php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
    ```
  </Step>

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

***

## Configurazione

`config/pennant.php` gestisce lo store:

| Driver     | Descrizione                      |
| ---------- | -------------------------------- |
| `database` | Persistente in DB (default)      |
| `array`    | In memoria (test/uso temporaneo) |

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

***

## Definizione delle feature

### Closure-based

Nel `boot` di un service provider:

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

<Info>
  Se la definizione è solo una Lottery puoi omettere la closure:

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

### Class-based

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

`app/Features/NewApi.php`:

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

namespace App\Features;

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

class NewApi
{
    public function resolve(User $user): mixed
    {
        return match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        };
    }
}
```

#### Nome memorizzato

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

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

#### Metodo `before`

Eseguito in memoria prima dello store; se non `null` prevale.

```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>
  Utile per kill switch o rollout schedulati.
</Tip>

***

## Verifica delle feature

### active / inactive

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

if (Feature::active('new-api')) {
    // Nuova API
}
```

Classe:

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

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

Altri metodi:

```php theme={null}
Feature::allAreActive(['new-api', 'site-redesign']);
Feature::someAreActive(['new-api', 'site-redesign']);
Feature::inactive('new-api');
Feature::allAreInactive(['new-api', 'site-redesign']);
Feature::someAreInactive(['new-api', 'site-redesign']);
```

### when / unless

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

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

### Trait HasFeatures

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

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

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

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

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

### Blade

```blade theme={null}
@feature('site-redesign')
    {{-- Attiva --}}
@else
    {{-- Non attiva --}}
@endfeature

@featureany(['site-redesign', 'beta'])
    {{-- Almeno una attiva --}}
@endfeatureany
```

### Middleware

Il middleware `EnsureFeaturesAreActive` richiede la feature: se inattiva restituisce `400`.

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

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

Response personalizzata:

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

### Cache in-memory

Pennant memorizza i risultati per richiesta.

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

***

## Scope

### Specifica dello scope

Default: utente autenticato. Con `for()`:

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

Esempio per team:

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

### Scope predefinito

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

Dopo:

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

### Scope nullable

Se lo scope è `null` (route senza autenticazione, Artisan), la definizione deve accettarlo altrimenti restituisce `false`.

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

***

## Valori ricchi

Non solo boolean:

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

Recupero:

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

Blade con valore:

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

<Info>
  Con valori ricchi, tutto ciò che non è `false` è considerato attivo.
</Info>

```php theme={null}
Feature::when('purchase-button',
    fn ($color) => /* riceve $color */,
    fn () => /* inattiva */,
);
```

***

## Più feature contemporaneamente

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

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

Tutte:

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

Per includere anche le class-based nel risultato:

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

***

## Eager loading

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

// OK
Feature::for($users)->load(['notifications-beta']);

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

Solo mancanti:

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

***

## Aggiornamento dei valori

### Manuale

```php theme={null}
Feature::activate('new-api');

Feature::for($user->team)->deactivate('billing-v2');

Feature::activate('purchase-button', 'seafoam-green');
```

Dimenticare per rivalutare:

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

### Bulk

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

### Purge

```php theme={null}
Feature::purge('new-api');
Feature::purge(['new-api', 'purchase-button']);
Feature::purge();
```

Comandi Artisan:

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

php artisan pennant:purge new-api purchase-button

php artisan pennant:purge --except=new-api --except=purchase-button

php artisan pennant:purge --except-registered
```

***

## Test

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

Classe:

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

### Store per i test

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

***

## Driver personalizzato

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

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

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

***

## Riepilogo

| Obiettivo     | Metodo                                       |
| ------------- | -------------------------------------------- |
| Installazione | `composer require laravel/pennant`           |
| Definizione   | `Feature::define('name', fn ($user) => ...)` |
| Verifica      | `Feature::active('name')`                    |
| Blade         | `@feature('name') ... @endfeature`           |
| Update        | `Feature::activate('name')` / `deactivate`   |
| Bulk          | `Feature::activateForEveryone('name')`       |
| Test          | `Feature::define('name', true)`              |
| Purge         | `Feature::purge('name')`                     |

## Prossimi passi

<Columns cols={2}>
  <Card title="Gestione errori" icon="circle-x" href="/it/error-handling">
    Gestione delle eccezioni.
  </Card>

  <Card title="Laravel Pulse" icon="chart-line" href="/it/pulse">
    Dashboard di monitoraggio.
  </Card>
</Columns>


## Related topics

- [Casi d'uso pratici di Laravel Pennant](/it/blog/laravel-pennant.md)
- [Laravel Boost](/it/boost.md)
- [Laravel Telescope](/it/telescope.md)
- [Laravel Bluesky](/it/packages/laravel-bluesky/index.md)
- [Laravel Octane](/it/octane.md)
