Wie Sie mit Illuminate\Support\Lottery probabilistische Operationen elegant umsetzen. Inklusive Anwendungsfälle in der Paketentwicklung und der Fixierung für Tests.
Illuminate\Support\Lottery ist eine Utility-Klasse, mit der Sie wahrscheinlichkeitsbasierte Operationen in einer Fluent API ausdrücken. Muster wie „nur jede 100. Anfrage ausführen” oder „nur bei einem Teil der Anfragen ausführliches Logging” lassen sich damit prägnant beschreiben.
Die Implementierung befindet sich in src/Illuminate/Support/Lottery.php. Laravel setzt diese Klasse selbst intern ein, etwa für Session-GC oder das Prunen abgelaufener Cache-Locks.
Da Lottery__invoke implementiert, können Sie eine Instanz direkt an APIs übergeben, die ein Callable erwarten.
// Beispiel: als zweites Argument von DB::whenQueryingForLongerThanDB::whenQueryingForLongerThan( Interval::seconds(5), Lottery::odds(1, 5)->winner(function ($connection) { // Bei einer erkannten Slow Query 1/5 der Fälle einen Alert senden Alert::send("Slow query on {$connection->getName()}"); }));
Laravel selbst nutzt intern in vielen Fällen probabilistische Wartungsroutinen. Einige Implementierungen entstanden vor der Einführung der Lottery-Klasse und verwenden direkt random_int() — der Grundgedanke ist derselbe.
1
Session: Garbage Collection
Illuminate\Session\Middleware\StartSession::configHitsLottery() liest die Einstellung lottery aus config/session.php und entscheidet mit random_int über die GC-Ausführung.
// config/session.php'lottery' => [2, 100], // 2 von 100 Anfragen// Intern in StartSession (nutzt direkt random_int)protected function configHitsLottery(array $config): bool{ return random_int(1, $config['lottery'][1]) <= $config['lottery'][0];}
2
DatabaseLock: Prunen abgelaufener Locks
Illuminate\Cache\DatabaseLock::acquire() löscht bei jedem Lock-Erwerb nach demselben Verhältnis abgelaufene Locks.
// config/cache.php (database-Treiber)'lock_lottery' => [2, 100], // 2 von 100 Fällen prunen// Intern in DatabaseLock::acquire() (nutzt direkt random_int)if (random_int(1, $this->lottery[1]) <= $this->lottery[0]) { $this->pruneExpiredLocks();}
3
DB::whenQueryingForLongerThan — Beispiel mit übergebener Lottery-Klasse
Da eine Lottery-Instanz als Callable verwendbar ist, lässt sie sich direkt als Slow-Query-Callback nutzen.
Während Session und DatabaseLock direkt random_int() nutzen, bietet die Lottery-Klasse den Vorteil, die Ergebnisse per alwaysWin(), alwaysLose() oder fix() für Tests zu steuern. In der Paketentwicklung erhöht die Wahl von Lottery die Testbarkeit.
Sie können die Ergebnisse mehrerer Aufrufe als Array aus true/false steuern.
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(); // Nach dem Test unbedingt zurücksetzen}
alwaysWin(), alwaysLose() und fix() verändern globale statische Eigenschaften. Rufen Sie in tearDown() unbedingt Lottery::determineResultNormally() auf.
protected function tearDown(): void{ Lottery::determineResultNormally(); parent::tearDown();}
Lottery::setResultFactory() — eigene Factory injizieren
Für feinere Kontrolle nutzen Sie eine eigene Factory.
Lottery::setResultFactory(function ($chances, $outOf) { // Eigene Logik, die immer gewinnt return true;});// Nach dem Test zurücksetzenLottery::determineResultNormally();