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

Seedspring Laravel Package

paragonie/seedspring

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer and inspect the single class that matters:

composer require paragonie/seedspring

Start by reading src/SeedSpring.php (it's small) and the README. The mental model is simple: you give it a seed string, and it hands back a deterministic stream of bytes and integers backed by AES-CTR rather than mt_rand/LCG. Same seed, same call sequence, same output — every time, in every process.

First use case: reproducible test data.

use ParagonIE\SeedSpring\SeedSpring;

$prng = new SeedSpring('fixture-user-42');

$bytes = $prng->getBytes(16);   // raw binary string
$roll  = $prng->getInt(1, 6);   // integer in [1, 6]

The sanity check every developer should run first:

$a = new SeedSpring('same-seed');
$b = new SeedSpring('same-seed');

$this->assertSame($a->getBytes(32), $b->getBytes(32));
// and, crucially:
$c = new SeedSpring('different-seed');
$this->assertNotSame($a->getBytes(32), $c->getBytes(32));

If that passes, you have the two properties you actually care about: determinism and seed sensitivity.

Implementation Patterns

Wrap it behind an interface. Don't sprinkle new SeedSpring(...) through your domain code. A tiny interface (RandomSource with getBytes/getInt) lets you swap in a fixed implementation for tests and a real seed in production, and keeps the dependency visible.

Bind it in a Laravel service provider, but choose the lifetime deliberately.

// app/Providers/RandomServiceProvider.php
$this->app->scoped(SeedSpring::class, function ($app) {
    return new SeedSpring($app['config']->get('simulation.seed'));
});

Use scoped (not singleton) so Octane/Swoole workers get a fresh stream per request. A singleton is fine only when you're deliberately continuing a stream across jobs (rare, and usually a mistake).

Isolate streams with derived sub-seeds. This is the single most useful pattern. Don't reuse one SeedSpring instance across unrelated consumers — their consumption order becomes coupled. Derive independent streams from a master seed:

function stream(string $masterSeed, string $purpose): SeedSpring
{
    return new SeedSpring(hash('sha256', $masterSeed . ':' . $purpose, true));
}

$dice    = stream($master, 'dice');
$avatars = stream($master, 'avatar-picker');

Now adding a new random consumer somewhere doesn't shift the outputs of existing ones.

Deterministic shuffle (Fisher–Yates on top of getInt):

function seededShuffle(array $items, SeedSpring $prng): array
{
    for ($i = count($items) - 1; $i > 0; $i--) {
        $j = $prng->getInt(0, $i);
        [$items[$i], $items[$j]] = [$items[$j], $items[$i]];
    }
    return $items;
}

Test fixtures. Seed once per test, assert against committed expected values. When you regenerate fixtures, commit the seed alongside them.

public function test_user_generation_is_stable(): void
{
    $prng = new SeedSpring('fixtures/users/v3');
    $names = array_map(
        fn () => 'User-' . bin2hex($prng->getBytes(4)),
        range(1, 25)
    );
    $this->assertSame($this->loadFixture('names.json'), $names);
}

Simulation / Monte Carlo. Store the seed with every run's results. A row in simulation_runs with seed, package_version, and output_hash turns a non-reproducible experiment into a replayable one.

PHP 8.2 Random\Randomizer integration. If the installed version exposes a Random\Engine adapter, you can hand it to new Random\Randomizer(...) and get shuffle(), getInt(), pickArrayKeys() etc. for free — check the source before relying on it.

Gotchas and Tips

Never use it for security. It's deterministic by design; that's the opposite of what you want for tokens, password resets, CSRF secrets, encryption keys, or anything user-guessable. AES-CTR is a strong primitive, but the seed is the key, and you'll be logging it. Use random_bytes()/random_int() for security.

Reproducibility is a function of the call sequence, not just the seed. getBytes(5) then getBytes(5) does not produce the same stream as getBytes(10) split in half. Any change in the order or sizes of calls shifts everything downstream. Treat the stream position as part of your test contract.

Interleaved consumers break silently. If two services share one instance, adding a call in service A changes service B's output. The sub-seed pattern above exists specifically to prevent this.

Don't seed from time() or uniqid() and then expect reproducibility. Derive the seed from a stored master, a config value, or a database column. Log the seed whenever you generate anything you might need to regenerate.

Pin the version. Determinism is a promise across identical code. If the package changes its KDF or counter layout in a major release, your fixtures and recorded runs will diverge. Use a tight constraint ("paragonie/seedspring": "1.0.*") in projects with committed fixtures.

Beware the container lifetime in Octane. A singleton binding keeps advancing the same counter across requests in a worker process. Use scoped, or instantiate per job in a queue worker.

Cloning copies the counter. clone $prng gives you a branch point — both copies continue identically until one consumes. Usually convenient, occasionally surprising; verify there are no internal resource handles before relying on it.

Cross-language reproduction is not free. Reproducing the stream in JS/Python/Rust requires matching the exact key derivation and AES-CTR block layout (counter endianness, starting value, block size). If you need cross-language parity, the safest route is to generate bytes once in PHP and ship them, rather than reimplementing the stream.

Range bias. getInt($min, $max) should be rejection-sampled, but if you're relying on it for a very unusual range or a non-power-of-two modulus, confirm the implementation in the source before assuming uniform distribution.

Empty or short seeds are legal but easy to misuse. An accidentally empty config value still yields a working PRNG — with a stream everyone can reproduce. Reject empty seeds loudly at boot.

Memory on large streams. getBytes() returns a full string. For gigabytes of deterministic data, loop in chunks (e.g. 4 KiB at a time) rather than requesting one huge block.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource