tourze/symfony-snowflake-bundle
Symfony bundle that integrates Snowflake-style distributed ID generation. It wires the generator into Symfony's service container, lets you configure worker and datacenter identifiers, and provides unique, time-sortable IDs for entities and services. A lightweight integration for scalable identifier...
This package is a Symfony bundle (tourze/symfony-snowflake-bundle), not a Laravel package. The first thing to understand is that you are not getting a service provider, a facade, or a publishable config file — you are getting a DI extension that registers a Snowflake ID generator service inside a Symfony kernel. In a Laravel app you will almost always consume the underlying generator, not the bundle itself.
Minimal path:
composer require tourze/symfony-snowflake-bundle and immediately inspect what it pulls in (composer show --tree tourze/symfony-snowflake-bundle). The bundle itself is thin; the real behaviour lives in its snowflake dependency.vendor/tourze/symfony-snowflake-bundle/src/*Bundle.php — confirms the service ids and whether anything is registered conditionally.vendor/tourze/symfony-snowflake-bundle/src/DependencyInjection/*Extension.php and .../Configuration.php — this is the closest thing to "documentation" for worker/datacenter/epoch parameters.vendor/tourze/symfony-snowflake-bundle/src/Resources/config/* — the service definitions you'll be mirroring.If you are actually running Symfony (e.g. a shared package across both), register the bundle in config/bundles.php and configure the worker id via the bundle's config key — then the rest of this assessment's patterns still apply.
Bind the generator as a singleton. Snowflake generators are stateful — they hold last-timestamp and sequence counters. Creating one per request or, worse, per call, guarantees collisions.
$this->app->singleton(SnowflakeGenerator::class, fn () => new SnowflakeGenerator(
workerId: (int) config('snowflake.worker_id'),
datacenterId: (int) config('snowflake.datacenter_id'),
// epoch is a decision you make once; never change it after data exists
));
Inject it by constructor wherever you need it; use app(...) only in legacy code.
Model primary keys. Keep $incrementing = false and $keyType = 'string' (or int if you're sure nothing serialises it to JSON), and generate in the creating hook — before the insert, so you can reference the id in related writes in the same transaction:
protected static function booted(): void
{
static::creating(function (self $model) {
$model->{$model->getKeyName()} ??= app(SnowflakeGenerator::class)->nextId();
});
}
Prefer this over HasUniqueStringIds — you want a BIGINT UNSIGNED, not a UUID-shaped string, and you want the generator injectable/testable.
Assign worker ids deliberately. The generator only guarantees uniqueness per (datacenter, worker) pair. Derive the id from something stable per process: SNOWFLAKE_WORKER_ID injected by your deploy manifest, the pod ordinal from a StatefulSet, or HOSTNAME hashed into the allowed bit-space — and fail loudly at boot if it isn't set. Never leave it defaulted to 0 in a multi-replica deployment.
Cast at the API boundary. Any id crossing into JavaScript must be a string. Use API Resources with (string) $model->id, and be aware json_encode on a raw int will silently truncate past 2^53.
Exploit the time-ordering. Snowflake ids sort by creation time, so ->latest('id') is a valid, index-friendly substitute for created_at in cursor pagination, and ranges of ids map to ranges of time. That's the main day-to-day payoff over UUIDv4.
Deterministic tests. Bind a fake sequence generator in the test container so assertions don't depend on wall-clock time or worker id.
config/bundles.php nothing happens — Laravel never reads that file. Expect to write a ~20-line service provider, and treat the bundle's Configuration.php as the spec for parameter names and bit-widths.symfony/dependency-injection / symfony/framework-bundle constraints can fight with the Symfony components Laravel already pins. Check composer why symfony/framework-bundle after install; if it's being added solely for the bundle, drop the bundle and use the underlying generator directly.nextId() so a backwards-clock exception is logged with the worker id attached.QueryException rather than quiet data corruption.unsignedBigInteger for MySQL, but note Postgres has no unsigned 64-bit integer — the high bit is unused by Snowflake anyway, so bigInteger is fine there. SQLite will store it as an integer but has no true 64-bit signed guarantee at the edges; fine for tests, not for production.IdGenerator interface, bind the bundle's service as the implementation, and wrap it with logging/metrics. That's also the seam that lets you swap in a ULID or a test double later.local/staging, boot-log the resolved worker id and datacenter id once per process (Log::info('snowflake', [...])) — a surprising share of "duplicate key" reports are just a mis-set env var.1.0.1...1.0.2) with no changelog entries, so there is nothing documented to assess for breaking changes. Before bumping, run the diff yourself (git diff 1.0.1 1.0.2 or the GitHub compare view) and focus on the generator dependency, since a patch bump there can change clock-rollback behaviour. If you need a known-good baseline, stay on 1.0.1 and pin it until the diff is reviewed. Because the package is pre-2.0 and the notes are empty, treat every patch release as potentially behaviour-changing and lock it in composer.lock with a deliberate review step.How can I help you explore Laravel packages today?