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

Mersenne Twister Laravel Package

ruafozy/mersenne-twister

A pure-PHP implementation of the Mersenne Twister (MT19937) pseudorandom number generator, offering seedable, deterministic random numbers for reproducible sequences or compatibility with existing MT output.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require ruafozy/mersenne-twister

The package is a straight port of the reference MT19937 implementation: one class, no service provider, no config file, no facade, no Blade directives. There is nothing to register — Composer autoloading is the entire integration. Look first at the single class file under vendor/ruafozy/mersenne-twister/src/; it exposes the classic reference primitives (int31(), int32(), real1()/real2()/real3(), res53()) over the 624-word internal state, seeded through the constructor. Everything else you need — range(), shuffle(), pick() — you build yourself.

The one reason to reach for this over mt_rand(), random_int(), or Str::random() is reproducibility: the same seed yields the same sequence on every machine, every PHP version, every deploy. PHP 7.1 silently changed the algorithm behind mt_rand() (the "modulo bias" fix), so native output is not stable across runtimes. This package pins the sequence.

First use case — a reproducible shuffle so a user's "randomly ordered" dashboard doesn't reshuffle on every refresh:

use Ruafozy\MersenneTwister\MersenneTwister;

$mt = new MersenneTwister(crc32("feed:{$user->id}"));

$order = $feedItems;
for ($i = count($order) - 1; $i > 0; $i--) {
    $j = $mt->int31() % ($i + 1);
    [$order[$i], $order[$j]] = [$order[$j], $order[$i]];
}

That six-line snippet is roughly 80% of real-world usage. Treat the package as a deterministic primitive, not a replacement for PHP's random functions.

Implementation Patterns

Bind it in the container, and use scoped(), not singleton().

// app/Providers/DeterministicRandomServiceProvider.php
public function register(): void
{
    $this->app->scoped(MersenneTwister::class, fn ($app) =>
        new MersenneTwister((int) $app['config']->get('app.det_random_seed', random_int(1, PHP_INT_MAX)))
    );
}

scoped() gives you one generator per request/job and is flushed between requests under Octane. singleton() under Octane leaks the stream across requests, which silently breaks per-request reproducibility.

Wrap it behind your own interface. This is the single highest-value pattern given the package's age and zero dependents. Define App\Support\Random\DeterministicRandom with int(), real(), shuffle(), pick(), inject it everywhere, and let the Mersenne Twister be an implementation detail. Swapping to a maintained port — or to native mt_rand() — later becomes a one-file change.

Add rejection sampling on top of int31(). Naive % $span introduces modulo bias; for a 6-sided die it's negligible, for a 1000-way bucketing it isn't:

public function int(int $min, int $max): int
{
    $span  = $max - $min + 1;
    $limit = intdiv(0x7FFFFFFF, $span) * $span;
    do { $n = $this->mt->int31(); } while ($n >= $limit);
    return $min + ($n % $span);
}

Derive seeds, don't store them. crc32($entity->id . ':' . $purpose) gives you stable, per-entity, per-feature seeds with no schema changes. Use a different purpose string per feature ('feed', 'ab:checkout-v2', 'sampling:analytics') so streams are independent. If the seed must be unguessable, use a keyed hash (hash_hmac('sha256', $id, config('app.key')) truncated to 32 bits) — but see the security caveat below.

Queue jobs: generate the seed at dispatch, pass it in the payload. The worker process has a different PRNG lifecycle; a seed embedded in the job makes the job's output reproducible even if it retries or is replayed from Horizon.

final class BuildRecommendations implements ShouldQueue
{
    public function __construct(public int $seed) {}
    public function handle(DeterministicRandom $random): void { /* seeded per job */ }
}

Testing is where this pays for itself. Snapshot tests, golden-file tests, and "this user got variant B in staging but A locally" bugs all disappear when the seed is fixed in phpunit.xml (<env name="DET_RANDOM_SEED" value="42"/>) or overridden per test with $this->app->instance(MersenneTwister::class, new MersenneTwister(42)).

Seeders and fixtures. Laravel's Faker is already seedable ($faker->seed()), so use this package for seeders only if you're generating derived data (shuffled pivot rows, deterministic timestamps) rather than Faker values. Don't double up.

A/B assignment. A short hash of user ID plus experiment name into real2() < $trafficFraction gives you stable, sticky, database-free bucketing — a legitimate production use case for this package.

Gotchas and Tips

  • It is not cryptographically secure. Never use it for password reset tokens, API keys, session IDs, invite codes, or anything an attacker benefits from predicting. random_bytes()/Str::random() remain the answer there. Derived seeds from user IDs are guessable by design.
  • Pure PHP is slow. Expect roughly one to two orders of magnitude slower than native mt_rand(). Fine for per-request shuffles of hundreds of items; do not put it in a loop over 100k rows. If you need bulk deterministic values, generate them once and cache them.
  • int32() returns signed values. You'll get negative numbers; mask with & 0xFFFFFFFF before doing unsigned arithmetic. Watch for overflow on 32-bit PHP builds, which still exist in some CI images.
  • Float precision. real1/2/3() give ~32 bits of precision; only res53() gives 53. Don't use real*() for high-precision sampling or for anything compared with strict equality on floats.
  • No state export/import unless the class exposes it. Check the source before designing around "continue the stream across jobs." If there's no getState()/setState(), the only safe unit of reproducibility is "one seed per unit of work."
  • Retries replay the sequence. A queued job with a fixed seed that retries will produce identical output — usually what you want, occasionally a subtle bug when combined with a persisted side effect. Make handlers idempotent.
  • Maintenance risk is real. 22 stars, zero dependents, no recent activity. Pin the version in composer.json, and keep the wrapper interface so you can vendor the ~200 lines or replace them. Verify it against your PHP version (8.2/8.3) before adopting — older ports sometimes emit deprecation notices from dynamic properties or removed functions; run the test suite with E_ALL once.
  • Cross-language parity is not automatic. MT19937 is standard, but the seeding routine is not (Python and NumPy use init_by_array, not init_genrand). If you're matching a Python service or a JS client, verify sequences explicitly with a known seed before committing to it.
  • Log your seeds. Add the seed to Log::withContext() or the job payload. "Why did this user see a different order?" is answerable in seconds if the seed is in the logs and unanswerable otherwise.
  • Extension points are just subclassing. The class is small and non-final as far as the design is concerned — extend it for shuffle(), weightedPick(), or getState(), or (preferred) compose it inside your own DeterministicRandom service and keep the dependency contained.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource