Use Illuminate\Support\Lottery to implement probabilistic operations with an elegant fluent API. Covers real-world use cases, Laravel internals, and deterministic testing.
Illuminate\Support\Lottery is a utility class that lets you express probability-based operations as a clean, fluent API. Instead of sprinkling random_int() calls across your codebase, you can write self-documenting code like “run this 1 in 100 times”.
The implementation lives in src/Illuminate/Support/Lottery.php. Laravel itself uses this class internally for session garbage collection, cache lock pruning, and more.
Lottery implements __invoke, so you can pass an instance directly anywhere a callable is expected.
// Pass as the second argument to DB::whenQueryingForLongerThanDB::whenQueryingForLongerThan( Interval::seconds(5), Lottery::odds(1, 5)->winner(function ($connection) { // Alert the team 1 in 5 times a slow query is detected Alert::send("Slow query on {$connection->getName()}"); }));
Laravel uses probabilistic maintenance patterns throughout the framework. Some implementations were written before the Lottery class existed and use random_int() directly, but they follow the same philosophy.
1
Session: garbage collection
Illuminate\Session\Middleware\StartSession::configHitsLottery() reads config/session.php and uses random_int to decide whether to run GC on the current request.
// config/session.php'lottery' => [2, 100], // 2 times per 100 requests// StartSession internals (uses random_int directly)protected function configHitsLottery(array $config): bool{ return random_int(1, $config['lottery'][1]) <= $config['lottery'][0];}
2
DatabaseLock: pruning expired locks
Illuminate\Cache\DatabaseLock::acquire() applies the same integer ratio pattern to prune stale lock rows on every lock acquisition.
While Session and DatabaseLock use random_int() directly, using the Lottery class gives you testability through alwaysWin(), alwaysLose(), and fix(). Prefer Lottery in your packages for this reason.
Control each draw individually by providing an array of true/false values.
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(); // always reset after the test}
alwaysWin(), alwaysLose(), and fix() all mutate a global static property. Always call Lottery::determineResultNormally() in tearDown() to prevent test pollution.
protected function tearDown(): void{ Lottery::determineResultNormally(); parent::tearDown();}