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

# Casi d'uso pratici di Laravel Pennant

> Presenta pattern pratici oltre le basi di Laravel Pennant. Introduce know-how che non emergono dalla sola documentazione ufficiale: scope a livello di team, kill switch di emergenza, dark launch, comandi di gestione e altro.

Per l'uso di base fai riferimento alla [pagina della guida](/it/pennant). In questa pagina presentiamo pattern pratici non presenti nella documentazione ufficiale.

***

## Scope di team in un SaaS multi-tenant

Se vuoi gestire i flag a livello di team (tenant) invece che di singolo utente, cambia lo scope predefinito in team.

```php theme={null}
// AppServiceProvider
Feature::resolveScopeUsing(fn () => Auth::user()?->currentTeam);
```

Basta questo perché `Feature::active('billing-v2')` si riferisca automaticamente al team corrente. Finché i membri appartengono allo stesso team ottengono lo stesso risultato, garantendo coerenza della UI.

Esempio di rollout graduale in base alla data di iscrizione del team.

```php theme={null}
use App\Models\Team;
use Illuminate\Support\Carbon;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;

Feature::define('new-billing', function (Team $team) {
    // I team registrati dal 2024 in poi sono subito abilitati
    if ($team->created_at->isAfter(new Carbon('2024-01-01'))) {
        return true;
    }

    // Iscrizioni 2022-2023, rollout al 10%
    if ($team->created_at->isAfter(new Carbon('2022-01-01'))) {
        return Lottery::odds(1 / 10);
    }

    // Team più vecchi: cautela, si parte dall'1%
    return Lottery::odds(1 / 100);
});
```

Se vuoi abilitarlo solo per team specifici (per esempio anteprima per clienti enterprise):

```php theme={null}
// Dopo il deploy, abilita manualmente solo per team specifici
Feature::for($earlyAccessTeam)->activate('new-billing');
```

***

## Kill switch di emergenza (uso del metodo `before`)

Quando in produzione emerge un bug, puoi disabilitare istantaneamente una funzionalità senza rollback del codice. Aggiungendo il metodo `before` a una feature basata su classe, il controllo viene eseguito prima del valore in storage.

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

namespace App\Features;

use App\Models\User;
use Illuminate\Support\Facades\Config;

class NewCheckout
{
    /**
     * Interruttore di emergenza che disabilita istantaneamente tramite config in caso di bug in produzione
     */
    public function before(User $user): mixed
    {
        // Controllato dal valore in config/features.php o da env
        if (Config::boolean('features.new_checkout_disabled', false)) {
            return false; // Disabilita istantaneamente per tutti
        }

        // Gli amministratori sono sempre abilitati (per il debug)
        if ($user->isAdmin()) {
            return true;
        }

        return null; // Restituendo null si passa al normale resolve()
    }

    public function resolve(User $user): mixed
    {
        return $user->isPremium() || Lottery::odds(1 / 5);
    }
}
```

Impostando la variabile d'ambiente `FEATURES_NEW_CHECKOUT_DISABLED=true` puoi fermare la funzionalità senza toccare il DB. Un kill switch senza deploy.

<Tip>
  Se `before` restituisce `null`, viene eseguito `resolve()`. Se restituisce `false`, viene trattato immediatamente come inattivo. Quando non è un'emergenza, restituisci `null`.
</Tip>

***

## Rollout schedulato

Se vuoi pubblicare automaticamente a tutti gli utenti a una data e ora specifiche, puoi implementarlo con il metodo `before`.

```php theme={null}
public function before(User $user): mixed
{
    $rolloutDate = Config::get('features.new_api.rollout_date');

    if ($rolloutDate && Carbon::parse($rolloutDate)->isPast()) {
        return true; // Passata la data di rollout, pubblicato a tutti
    }

    return null; // Se non ancora, passa al normale resolve()
}

public function resolve(User $user): mixed
{
    // Prima del rollout, solo membri interni
    return $user->isInternalTeamMember();
}
```

```ini theme={null}
# .env
FEATURES_NEW_API_ROLLOUT_DATE=2025-04-01
```

Così, superato il `2025-04-01`, la funzionalità viene automaticamente pubblicata a tutti. Automatizzabile senza deploy, senza DB, senza comandi Artisan.

***

## Dark launch (Shadow Mode)

Pattern che fa girare una nuova logica sui dati di produzione senza mostrarla agli utenti, confrontando i risultati con la logica precedente. Se non ci sono problemi, basta accendere il flag per completare il rilascio.

```php theme={null}
class RecommendationController
{
    public function index(Request $request)
    {
        $legacyResult = $this->getLegacyRecommendations($request->user());

        // In shadow mode esegui il nuovo algoritmo per confrontarlo, ma mostra la vecchia logica
        if (Feature::for($request->user())->active('recommendation-v2-shadow')) {
            try {
                $newResult = $this->getNewRecommendations($request->user());

                // Registra la differenza nei log (invisibile all'utente)
                if ($legacyResult !== $newResult) {
                    Log::channel('shadow_mode')->info('recommendation diff', [
                        'user_id' => $request->user()->id,
                        'legacy'  => $legacyResult,
                        'new'     => $newResult,
                    ]);
                }
            } catch (\Throwable $e) {
                Log::error('shadow mode error', ['error' => $e->getMessage()]);
            }
        }

        // La risposta è sempre il risultato della vecchia logica
        return response()->json($legacyResult);
    }
}
```

Una volta verificato dai log che non ci sono più differenze, basta cambiare il flag in `recommendation-v2`.

***

## Raccolta dei risultati A/B test tramite eventi

La documentazione ufficiale accenna all'evento `FeatureRetrieved`, ma non mostra un pattern reale di aggregazione A/B.

L'evento `FeatureResolved` si attiva **solo la prima volta** che il valore della feature viene risolto. Usalo per registrare l'assegnazione della variante a un utente.

```php theme={null}
use Laravel\Pennant\Events\FeatureResolved;
use Illuminate\Support\Facades\Event;

// Registra in AppServiceProvider o EventServiceProvider
Event::listen(function (FeatureResolved $event) {
    if ($event->feature !== 'purchase-button') {
        return;
    }

    // Registra al momento dell'assegnazione della variante all'utente
    analytics()->identify($event->scope?->id, [
        'ab_purchase_button' => $event->value,
    ]);
});
```

Se registri separatamente il momento della conversione, potrai aggregare il tasso di conversione per ogni variante.

```php theme={null}
// Al completamento dell'acquisto
Event::dispatch(new PurchaseCompleted($user, $product));
```

```php theme={null}
// Listener PurchaseCompleted
analytics()->track('purchase_completed', [
    'user_id'              => $user->id,
    'ab_purchase_button'   => Feature::for($user)->value('purchase-button'),
]);
```

<Info>
  Differenza tra `FeatureResolved` e `FeatureRetrieved`: `FeatureResolved` si attiva solo alla prima valutazione, `FeatureRetrieved` a ogni verifica. Per registrare l'assegnazione è più adatto `FeatureResolved`, per tracciare le page view `FeatureRetrieved`.
</Info>

***

## Scope nei job in coda

Nei job di coda non esiste un utente autenticato, quindi il controllo delle feature può comportarsi in modo inatteso. Passa esplicitamente lo scope al job.

```php theme={null}
class SendWeeklyDigest implements ShouldQueue
{
    public function __construct(
        private readonly User $user
    ) {}

    public function handle(): void
    {
        // NO: non c'è utente autenticato, quindi sarà sempre false
        // if (Feature::active('new-digest-layout')) { ... }

        // OK: passa esplicitamente l'utente
        if (Feature::for($this->user)->active('new-digest-layout')) {
            $this->sendNewLayout($this->user);
        } else {
            $this->sendLegacyLayout($this->user);
        }
    }
}
```

Puoi anche valutare il flag al momento del dispatch del job e passarlo al job stesso.

```php theme={null}
// Valuta nel controller e passa al job
$useNewLayout = Feature::for($user)->active('new-digest-layout');

SendWeeklyDigest::dispatch($user, $useNewLayout);
```

***

## UI di gestione tramite comando Artisan

Se crei un semplice comando Artisan per gestire i flag, puoi manipolare quelli di produzione senza deploy.

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

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Laravel\Pennant\Feature;

class ManageFeatureFlag extends Command
{
    protected $signature = 'feature {action : activate|deactivate|status} {feature : Feature name}';
    protected $description = 'Gestisce i feature flag';

    public function handle(): void
    {
        $feature = $this->argument('feature');

        match ($this->argument('action')) {
            'activate'   => $this->activate($feature),
            'deactivate' => $this->deactivate($feature),
            'status'     => $this->status($feature),
            default      => $this->error('Azione sconosciuta'),
        };
    }

    private function activate(string $feature): void
    {
        Feature::activateForEveryone($feature);
        $this->info("✓ {$feature} attivato per tutti gli utenti");
    }

    private function deactivate(string $feature): void
    {
        Feature::deactivateForEveryone($feature);
        $this->info("✓ {$feature} disattivato per tutti gli utenti");
    }

    private function status(string $feature): void
    {
        $count = \DB::table('features')
            ->where('name', $feature)
            ->where('value', json_encode(true))
            ->count();

        $this->line("{$feature}: attivo su {$count} scope");
    }
}
```

```shell theme={null}
php artisan feature activate new-checkout
php artisan feature deactivate new-checkout
php artisan feature status new-checkout
```

***

## Refactoring sicuro dei nomi delle feature (attributo `Name`)

Quando rinomini una feature class, se cambia il nome del flag salvato nel DB tutti i flag degli utenti vengono resettati. Con l'attributo `Name` fissa il nome usato per lo storage.

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

// Vecchio nome classe: CheckoutV2 → nuovo nome: NewCheckoutExperience
// Nel DB viene salvato sempre come 'checkout-v2'
#[Name('checkout-v2')]
class NewCheckoutExperience
{
    public function resolve(User $user): mixed
    {
        return $user->isPremium();
    }
}
```

In questo modo puoi rifattorizzare liberamente il nome della classe mantenendo i dati del DB.

***

## Conclusioni

Una volta padroneggiate le basi della documentazione ufficiale, il vero valore di Pennant emerge con i pattern seguenti.

| Pattern                     | Contesto d'uso                                      |
| --------------------------- | --------------------------------------------------- |
| Scope di team               | SaaS multi-tenant                                   |
| Kill switch `before`        | Risposta d'emergenza a bug in produzione            |
| Rollout schedulato          | Automazione di rilasci a data e ora specifiche      |
| Dark launch                 | Validazione sicura di nuovi algoritmi in produzione |
| Evento `FeatureResolved`    | Raccolta precisa dei risultati A/B test             |
| Scope esplicito nei job     | Valutazione corretta dei flag in ambiente di coda   |
| Comando Artisan di gestione | Manipolazione dei flag di produzione senza deploy   |
| Attributo `Name`            | Refactoring sicuro della classe                     |

<Card title="Guida Laravel Pennant" icon="flag" href="/it/pennant">
  Per l'installazione e l'uso di base fai riferimento alla pagina della guida.
</Card>


## Related topics

- [Scope di Eloquent](/it/advanced/eloquent-scopes.md)
- [Trait InteractsWithData](/it/advanced/interacts-with-data.md)
- [Tecniche pratiche di Laravel Telescope](/it/blog/telescope-introduction.md)
- [Core — operazioni core di AT Protocol](/it/packages/laravel-bluesky/core.md)
- [Confronto tra metodi di autenticazione - Laravel Bluesky](/it/packages/laravel-bluesky/authentication.md)
