Zum Hauptinhalt springen

Was ist die Lottery-Klasse?

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.

Grundlegende Verwendung

Wahrscheinlichkeit als Ganzzahl-Verhältnis

Mit Lottery::odds($chances, $outOf) geben Sie die Wahrscheinlichkeit als „chancesvonchances von outOf Fällen” an.
use Illuminate\Support\Lottery;

Lottery::odds(1, 100)           // 1 von 100 Fällen
    ->winner(fn () => $this->runMaintenance())
    ->loser(fn () => null)
    ->choose();

Wahrscheinlichkeit als Dezimalzahl

Wenn Sie $outOf weglassen und einen Dezimalwert zwischen 0.0 und 1.0 übergeben, wird dieser als Wahrscheinlichkeit verwendet.
Lottery::odds(0.01)             // 1 % Wahrscheinlichkeit
    ->winner(fn () => $this->sample())
    ->choose();
Übersteigt der Dezimalwert 1.0, wird eine RuntimeException geworfen.

Boolean-Rückgabe ohne Callbacks

Ohne gesetzte winner-/loser-Callbacks liefert choose() true im Gewinnfall und false andernfalls.
$shouldSample = Lottery::odds(1, 50)->choose(); // bool

Mehrmalige Ausführung

Übergeben Sie choose($times) eine Anzahl, wird ein Array mit den Ergebnissen zurückgegeben.
$results = Lottery::odds(1, 2)
    ->winner(fn () => 'win')
    ->loser(fn () => 'lose')
    ->choose(10);

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

Als Callable weitergeben

Da Lottery __invoke implementiert, können Sie eine Instanz direkt an APIs übergeben, die ein Callable erwarten.
// Beispiel: als zweites Argument von DB::whenQueryingForLongerThan
DB::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()}");
    })
);

Praktische Anwendungsfälle

1. Cache Prunen (nur 1 von 100 Fällen)

Ideal für Wartungsaufgaben wie das Entfernen abgelaufener Einträge, die nicht bei jedem Aufruf laufen müssen.
Lottery::odds(1, 100)
    ->winner(fn () => Cache::store('database')->flush())
    ->choose();

2. Telemetrie-Sampling (Detail-Logs nur für einen Teil der Anfragen)

Wenn das Loggen jeder Anfrage zu teuer ist, eignet sich 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. A/B-Test-artiges Verhalten

Nutzer werden probabilistisch auf zwei Code-Pfade verteilt.
$result = Lottery::odds(1, 2)
    ->winner(fn ($user) => $this->newCheckoutFlow($user))
    ->loser(fn ($user) => $this->legacyCheckoutFlow($user))
    ->choose();

4. Als Ergänzung zum Scheduler zufällige Tasks ausführen

Nützlich, wenn Sie einen Task zufällig ausführen wollen, ohne dass mehrere Server ihn doppelt starten.
// app/Console/Kernel.php
$schedule->call(function () {
    Lottery::odds(1, 3)
        ->winner(fn () => Artisan::call('cache:prune-stale-tags'))
        ->choose();
})->everyMinute();

Probabilistische Muster im Laravel-Framework

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.
DB::whenQueryingForLongerThan(
    Interval::seconds(5),
    Lottery::odds(1, 5)->winner(function ($connection) {
        Log::warning("Slow query on {$connection->getName()}");
    })
);
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.

Verwendung in Tests

Für Tests von Code mit Zufall nutzen Sie die Test-API von Lottery.

Lottery::alwaysWin() — immer gewinnen

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() — immer verlieren

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() — Ergebnisse als Sequenz festlegen

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ücksetzen
Lottery::determineResultNormally();

Einsatz in der Paketentwicklung

Registrierung im Service Provider

Wenn Sie Wartungsroutinen in den Service Provider Ihres Pakets einbauen, verteilen Sie mit Lottery die Last.
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();
    }
}

Wahrscheinlichkeit aus der Konfiguration lesen

Machen Sie die Wahrscheinlichkeit über eine Config-Datei änderbar, damit Nutzer sie leichter anpassen können.
$lottery = config('acme.prune_lottery', [1, 100]);

Lottery::odds(...$lottery)
    ->winner(fn () => $this->pruneExpiredRecords())
    ->choose();
// config/acme.php
return [
    // [Gewinne, Versuche] = 1 von 100 Anfragen prunen
    'prune_lottery' => [1, 100],
];

Sampling in einer 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-Referenz

MethodeBeschreibung
Lottery::odds($chances, $outOf)Statische Factory, erzeugt eine Lottery-Instanz
->winner(callable $callback)Callback im Gewinnfall setzen
->loser(callable $callback)Callback im Verlustfall setzen
->choose($times = null)Lottery ausführen; mit $times wird ein Array zurückgegeben
Lottery::alwaysWin($callback)Für Tests: immer gewinnen
Lottery::alwaysLose($callback)Für Tests: immer verlieren
Lottery::fix($sequence)Für Tests: Ergebnisse als Sequenz fixieren
Lottery::determineResultNormally()Testfixierung zurücksetzen
Lottery::setResultFactory(callable)Eigene Entscheidungslogik injizieren

Verwandte Seiten

Macroable-Trait

Lernen Sie das Erweiterungsmuster kennen, um bestehenden Klassen neue Methoden hinzuzufügen.

Conditionable-Trait

Lernen Sie den Entwurf von Bedingungs-Chains mit when() / unless() kennen.
Zuletzt geändert am 13. Juli 2026