Come implementare in modo elegante operazioni probabilistiche con Illuminate\Support\Lottery. Include casi d’uso per lo sviluppo di pacchetti e il fissaggio dei risultati nei test.
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.
L’istanza Lottery implementa __invoke, quindi la puoi passare direttamente alle API che accettano un callable.
// esempio: la passi come secondo argomento di DB::whenQueryingForLongerThanDB::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()}"); }));
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();}
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à.
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 resettaLottery::determineResultNormally();