Vai al contenuto principale

Cos’è la classe Lottery

Illuminate\Support\Lottery è una classe di utilità che esprime operazioni basate sulla probabilità con un’API fluent. Permette di scrivere in modo semplice pattern come “esegui un’elaborazione una volta ogni 100 richieste” o “registra i log dettagliati solo per una parte delle richieste”.
L’implementazione si trova in src/Illuminate/Support/Lottery.php. Laravel usa questa classe internamente per la GC delle sessioni, la pulizia dei lock di cache e altro.

Uso di base

Indicare la probabilità come rapporto intero

Con Lottery::odds($chances, $outOf) indichi la probabilità “chancesvincitesuchances vincite su outOf tentativi”.
use Illuminate\Support\Lottery;

Lottery::odds(1, 100)           // 1 volta su 100
    ->winner(fn () => $this->runMaintenance())
    ->loser(fn () => null)
    ->choose();

Indicare la probabilità come decimale

Omettendo $outOf e passando un decimale tra 0.0 e 1.0, il valore viene usato direttamente come probabilità.
Lottery::odds(0.01)             // probabilità dell'1%
    ->winner(fn () => $this->sample())
    ->choose();
Nella forma decimale, se il valore supera 1.0 viene sollevata una RuntimeException.

Restituire un booleano senza callback

Se non imposti winner / loser, choose() restituisce true in caso di vincita e false in caso di perdita.
$shouldSample = Lottery::odds(1, 50)->choose(); // bool

Eseguire più volte

Passando il numero di ripetizioni a choose($times) ottieni un array di risultati.
$results = Lottery::odds(1, 2)
    ->winner(fn () => 'win')
    ->loser(fn () => 'lose')
    ->choose(10);

// esempio: ['win', 'lose', 'win', 'win', 'lose', ...]

Passarlo come callable

L’istanza Lottery implementa __invoke, quindi la puoi passare direttamente alle API che accettano un callable.
// esempio: la passi come secondo argomento di DB::whenQueryingForLongerThan
DB::whenQueryingForLongerThan(
    Interval::seconds(5),
    Lottery::odds(1, 5)->winner(function ($connection) {
        // quando viene rilevata una query lenta, invia un alert con probabilità 1/5
        Alert::send("Slow query on {$connection->getName()}");
    })
);

Casi d’uso pratici

1. Prune della cache (eseguito 1 volta su 100)

Ideale per operazioni di manutenzione che non è necessario eseguire sempre, come la rimozione dei record scaduti.
Lottery::odds(1, 100)
    ->winner(fn () => Cache::store('database')->flush())
    ->choose();

2. Sampling telemetrico (log dettagliati solo su una parte delle richieste)

Se registrare a log tutte le richieste è troppo costoso, puoi usarlo per il sampling.
Lottery::odds(1, 20)
    ->winner(function () use ($request) {
        Log::channel('telemetry')->info('Request sampled', [
            'url'      => $request->url(),
            'duration' => microtime(true) - LARAVEL_START,
            'memory'   => memory_get_peak_usage(true),
        ]);
    })
    ->choose();

3. Comportamento tipo A/B test

Distribuisci probabilisticamente gli utenti su due percorsi di codice.
$result = Lottery::odds(1, 2)
    ->winner(fn ($user) => $this->newCheckoutFlow($user))
    ->loser(fn ($user) => $this->legacyCheckoutFlow($user))
    ->choose();

4. Esecuzione randomizzata di task periodici a supporto dello scheduler

Utile quando vuoi eseguire un task in modo casuale su più server evitando esecuzioni duplicate.
// app/Console/Kernel.php
$schedule->call(function () {
    Lottery::odds(1, 3)
        ->winner(fn () => Artisan::call('cache:prune-stale-tags'))
        ->choose();
})->everyMinute();

Pattern probabilistici nel framework Laravel

Laravel usa ampiamente pattern probabilistici anche internamente per operazioni di manutenzione. Alcune implementazioni, scritte prima dell’introduzione della classe Lottery, usano direttamente random_int(), ma seguono la stessa idea.
1

Session: garbage collection

Illuminate\Session\Middleware\StartSession::configHitsLottery() usa l’impostazione lottery di config/session.php ed esegue la GC verificando la probabilità con random_int.
// config/session.php
'lottery' => [2, 100], // 2 volte ogni 100 richieste

// interno di StartSession (usa direttamente random_int)
protected function configHitsLottery(array $config): bool
{
    return random_int(1, $config['lottery'][1]) <= $config['lottery'][0];
}
2

DatabaseLock: prune dei lock scaduti

Illuminate\Cache\DatabaseLock::acquire() a ogni acquisizione applica lo stesso pattern probabilistico per rimuovere i lock scaduti.
// config/cache.php (driver database)
'lock_lottery' => [2, 100], // esegue il prune 2 volte su 100

// interno di DatabaseLock::acquire() (usa direttamente random_int)
if (random_int(1, $this->lottery[1]) <= $this->lottery[0]) {
    $this->pruneExpiredLocks();
}
3

DB::whenQueryingForLongerThan — passare un'istanza Lottery

Poiché un’istanza Lottery è callable, puoi usarla direttamente come callback di rilevamento delle query lente.
DB::whenQueryingForLongerThan(
    Interval::seconds(5),
    Lottery::odds(1, 5)->winner(function ($connection) {
        Log::warning("Slow query on {$connection->getName()}");
    })
);
Session e DatabaseLock usano direttamente random_int(), mentre la classe Lottery ti permette di controllare il risultato nei test con alwaysWin() / alwaysLose() / fix(). Nello sviluppo di pacchetti la scelta di Lottery migliora la testabilità.

Utilizzo nei test

Per testare codice che dipende dalla casualità usa le API di test fornite da Lottery.

Lottery::alwaysWin() — vinci sempre

public function test_maintenance_runs_on_win(): void
{
    $ranMaintenance = false;

    Lottery::alwaysWin(function () use (&$ranMaintenance) {
        Lottery::odds(1, 100)
            ->winner(function () use (&$ranMaintenance) {
                $ranMaintenance = true;
            })
            ->choose();
    });

    $this->assertTrue($ranMaintenance);
}

Lottery::alwaysLose() — perdi sempre

public function test_maintenance_skipped_on_lose(): void
{
    $ranMaintenance = false;

    Lottery::alwaysLose(function () use (&$ranMaintenance) {
        Lottery::odds(1, 100)
            ->winner(function () use (&$ranMaintenance) {
                $ranMaintenance = true;
            })
            ->choose();
    });

    $this->assertFalse($ranMaintenance);
}

Lottery::fix() — fissare il risultato con una sequenza

Puoi controllare l’esito di più chiamate con un array di true/false.
public function test_alternating_results(): void
{
    Lottery::fix([true, false, true, false]);

    $results = Lottery::odds(1, 100)
        ->winner(fn () => 'winner')
        ->loser(fn () => 'loser')
        ->choose(4);

    $this->assertSame(['winner', 'loser', 'winner', 'loser'], $results);

    Lottery::determineResultNormally(); // dopo il test ripristina sempre
}
alwaysWin() / alwaysLose() / fix() modificano proprietà statiche globali. Chiama sempre Lottery::determineResultNormally() nel tearDown() del test.
protected function tearDown(): void
{
    Lottery::determineResultNormally();

    parent::tearDown();
}

Lottery::setResultFactory() — iniettare una factory personalizzata

Se serve un controllo più fine usa una factory personalizzata.
Lottery::setResultFactory(function ($chances, $outOf) {
    // logica personalizzata che vince sempre
    return true;
});

// dopo il test resetta
Lottery::determineResultNormally();

Utilizzo nello sviluppo di pacchetti

Registrazione nel service provider

Quando integri elaborazioni di manutenzione nel service provider di un pacchetto, usa Lottery per distribuire il carico.
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;

class AcmeServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->app->booted(function () {
            Lottery::odds(1, 100)
                ->winner(fn () => $this->pruneExpiredRecords())
                ->choose();
        });
    }

    protected function pruneExpiredRecords(): void
    {
        $this->app['db']->table('acme_logs')
            ->where('expires_at', '<', now())
            ->delete();
    }
}

Leggere gli odds dalla configurazione

Rendendo la probabilità modificabile dal file di configurazione, gli utenti possono regolarla facilmente.
$lottery = config('acme.prune_lottery', [1, 100]);

Lottery::odds(...$lottery)
    ->winner(fn () => $this->pruneExpiredRecords())
    ->choose();
// config/acme.php
return [
    // [vincite, tentativi] = 1 prune ogni 100 richieste
    'prune_lottery' => [1, 100],
];

Sampling nel middleware

use Illuminate\Support\Lottery;

class SampleTelemetryMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        Lottery::odds(1, 50)
            ->winner(fn () => $this->recordTelemetry($request, $response))
            ->choose();

        return $response;
    }
}

API reference

MetodoDescrizione
Lottery::odds($chances, $outOf)Factory statica che crea un’istanza Lottery
->winner(callable $callback)Imposta il callback in caso di vincita
->loser(callable $callback)Imposta il callback in caso di perdita
->choose($times = null)Esegue la lottery. Se indichi $times restituisce un array
Lottery::alwaysWin($callback)Per test: vinci sempre
Lottery::alwaysLose($callback)Per test: perdi sempre
Lottery::fix($sequence)Per test: fissa i risultati come sequenza
Lottery::determineResultNormally()Reset del fissaggio per test
Lottery::setResultFactory(callable)Inietta una logica di estrazione personalizzata

Pagine correlate

Trait Macroable

Impara il pattern di estensione per aggiungere metodi a classi esistenti.

Trait Conditionable

Impara a progettare catene condizionali con when() / unless().
Ultima modifica il 13 luglio 2026