Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Luckybox Laravel Package

cloned/luckybox

Laravel package for adding lucky-box and mystery-draw mechanics to your app: define prize pools, weights, and stock, then draw winners with configurable odds. Simple setup and helpers for running giveaways, promotions, and randomized rewards.

Deep Wiki
Context7

Getting Started

LuckyBox is a single-purpose library: give it items with weights, get one back with matching probability. Treat it as a ~50-line utility class, not a framework — there is no service provider, facade, or config file to publish, so integration is entirely on you.

composer require cloned/luckybox

Look at these in order:

  1. README.md — the entire public API fits in a handful of lines.
  2. src/ — the package is small enough to read end-to-end before you use it. Confirm the exact method names (add/draw/get/lottery vary across versions) and the return type (item vs. key vs. index).
  3. composer.json — check the PHP constraint and the last release date. With ~12 stars and a low activity score, pin the version in composer.json and don't expect upstream fixes.

First use case — a weighted pick:

use Cloned\LuckyBox\LuckyBox;

$box = new LuckyBox();
$box->add('common', 70);
$box->add('rare', 25);
$box->add('legendary', 5);

$prize = $box->draw(); // verify method name against the source

If a two-minute read of src/ shows the implementation is mt_rand(1, array_sum($weights)) plus a linear scan, that is worth knowing up front — it should influence whether you depend on the package at all versus inlining the fifteen lines.

Implementation Patterns

Wrap it behind a domain service. Never let LuckyBox leak into controllers or models. One class, one responsibility, and a seam you can swap if the package is abandoned:

namespace App\Services;

use Cloned\LuckyBox\LuckyBox;

class LootTable
{
    public function __construct(private array $weights) {}

    public function pick(): string
    {
        $box = new LuckyBox();
        foreach ($this->weights as $item => $weight) {
            if ($weight > 0) {
                $box->add($item, $weight);
            }
        }

        $winner = $box->draw();

        event(new PrizeAwarded($winner)); // or Log::info(), metrics, etc.

        return $winner;
    }
}

Drive weights from config, not code. Loot tables and rollout percentages change far more often than the code around them:

// config/loot.php
return [
    'chest_common' => ['gold' => 60, 'gem' => 30, 'skin' => 10],
];

Bind it in a provider so tests can override the table:

$this->app->bind('loot.common', fn () => new LootTable(config('loot.chest_common')));

Rebuild the box when weights change. The mutable add() API makes it tempting to keep one long-lived instance in a singleton and keep appending to it. Don't — weights accumulate, and a singleton shared across a request can drift. Construct per draw, or make the wrapper immutable and construct internally on each pick().

Use it for bucketed rollouts (A/B tests, canary features, queue prioritisation) where the "items" are variant names and the weights are traffic splits. For request-scoped bucketing you generally want deterministic assignment per user — crc32($userId) % 100 — not a fresh random draw. LuckyBox is the wrong tool there; note the distinction before you reach for it.

Test statistically, not by exact value. Unless the library exposes a seedable RNG (most don't), you cannot assert draw() === 'rare'. Assert distribution instead, with a tolerance wide enough to avoid flakiness:

$counts = ['a' => 0, 'b' => 0];
for ($i = 0; $i < 10000; $i++) {
    $counts[$this->table->pick()]++;
}
$this->assertEqualsWithDelta(0.25, $counts['a'] / 10000, 0.03);

Keep N high and the delta generous; a 2% tolerance on 1,000 runs will flake in CI. For unit tests of the calling code, bind a fake LootTable that returns a fixed value and skip the randomness entirely.

Gotchas and Tips

  • Weights are relative, not percentages. [70, 25, 5] and [7, 2.5, 0.5] behave identically. Don't build logic that assumes they sum to 100 — normalise only for display.
  • Float weights are a trap. Verify whether the implementation casts to int or uses mt_rand (integer-only). Passing 0.5 may silently truncate to 0 and make the item unselectable. Stick to integers unless the source clearly handles floats.
  • Zero and negative weights. Guard before calling add(). Negative weights corrupt array_sum and can make the range invalid, yielding an exception or an undefined index depending on the release.
  • Empty box. Check what happens with no items — likely an exception, possibly a false/null. Always provide a fallback item rather than letting a prize wheel 500 for a user.
  • Not cryptographically secure. If the outcome has monetary or regulatory weight (real prizes, paid gacha), mt_rand is not acceptable. Use random_int() yourself or verify the library uses it. Disclose the mechanism if it's user-facing.
  • Sampling without replacement isn't supported. Drawing three distinct winners means drawing, removing the item, and re-drawing in a loop — don't draw three times and array_unique, that skews the distribution.
  • O(n) per draw. Fine for dozens or hundreds of items; if your table has tens of thousands of entries and you draw per-request, precompute a cumulative array and binary-search it instead.
  • No Laravel auto-discovery. Nothing registers itself. If you want a facade, write a thin one, but a typed service class is usually enough and easier to test.
  • Concurrency. The instance is plain in-memory PHP state — never share it across processes or persist it into a cache/session. Serialise the weights, not the object.
  • Bus-factor risk. With a low star count and no recent activity, assume no upstream support. Pin the version, keep the wrapper thin, and accept that replacing it later is a one-file change — which is exactly why the wrapper exists.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names