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

Eloquent Keygen Laravel Package

jetcod/eloquent-keygen

Laravel package that integrates Snowflake ID generation with Eloquent models, giving you automatic, distributed, unique primary keys via a base model or trait, with per-model opt-out and configurable settings for scalable applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require jetcod/eloquent-keygen
php artisan vendor:publish   # pick the jetcod/eloquent-keygen entry interactively

Do not paste the README's --provider="eloquent-key-generator-config" into a runbook — that string is not a service provider class name and the command will fail. If you need the FQCN for automation, get it from the source:

grep -rl "ServiceProvider" vendor/jetcod/eloquent-keygen/src
ls config | grep -i -E 'keygen|snowflake'   # confirm the published config filename

Check the runtime matrix before you commit to a date. v1.2.0 requires 64-bit PHP 8.1+, Laravel 10–13, and godruoyi/php-snowflake ≥ 3.2. Generation moved from saving to creating, and PrimaryKeyGenerator::generate() now declares : string.

Read three files first — the package is small enough that this is minutes, not hours:

  1. The Snowflake trait (vendor/jetcod/eloquent-keygen/src/Traits/Snowflake.php) — where the creating hook is registered and whether it sets $incrementing / $keyType for you.
  2. The PrimaryKeyGenerator class — the public seam you'll call directly for bulk inserts and tests.
  3. The published config — the only values you'll actually operate.

First use case: one new, non-critical table. Pick a table that hasn't shipped.

// database/migrations/xxxx_create_orders_table.php
Schema::create('orders', function (Blueprint $table) {
    $table->unsignedBigInteger('id')->primary();   // NOT $table->id()
    $table->unsignedBigInteger('customer_id');
    $table->timestamps();
});
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Jetcod\Eloquent\Traits\Snowflake;

class Order extends Model
{
    use Snowflake;
}

Verify in tinker before writing a single feature test:

>>> (new App\Models\Order)->getIncrementing();  // must be false
>>> (new App\Models\Order)->getKeyType();       // ideally "string"
>>> App\Models\Order::factory()->create()->id;  // a ~19-digit string

If getIncrementing() is true, do not proceed — see the first gotcha below.

Implementation Patterns

Prefer the trait over extending Jetcod\Eloquent\Model. The base class works, but it puts a third-party class into your model hierarchy, which makes the fallback (copying ~30 lines of trait) a refactor instead of a copy-paste. The trait keeps Illuminate\Database\Eloquent\Model as the parent, so every IDE, static-analysis rule, and package that sniffs for Illuminate\Database\Eloquent\Model keeps working.

Keep the package as the convention, not the architecture. Treat it as a replaceable utility: pin the exact version, and know that vendoring the trait is roughly half a day if the maintainer goes quiet. Two stars, zero dependents, one maintainer — fine for a pilot, not for a platform-strategy slide.

Mixed-schema rollout (legacy auto-increment tables + new Snowflake tables). This is the realistic adoption path, and the per-model escape hatch makes it safe:

class LegacyInvoice extends Model   // extends the package base model
{
    protected function snowflake(): bool
    {
        return false;   // keep auto-increment while you plan the backfill
    }
}

Pilot on one service, revert per model. That reversibility is the whole reason this is approvable.

Generating IDs before commit. This is the actual day-to-day win — queued jobs, outbox rows, and event payloads that need to reference a row that doesn't exist yet:

$generator = app(\Jetcod\Eloquent\Generators\PrimaryKeyGenerator::class); // confirm FQCN in source
$id = $generator->generate();   // string

CreateOrderJob::dispatch($id, $payload);

Bulk imports. Model::insert(), upsert(), insertOrIgnore(), and DB::table()->insert() bypass model events entirely, so no IDs are generated. Either loop with Model::create() per chunk, or generate IDs yourself and include them in the insert payload:

$rows = collect($csv)->map(fn ($r) => [
    'id'         => $generator->generate(),
    'customer_id'=> $r['customer_id'],
    'created_at' => now(),
    'updated_at' => now(),
]);

Order::insert($rows->chunk(500)->first()->all());

Worker/datacenter allocation is a deployment concern, not a package concern. Set SNOWFLAKE_WORKER_ID per process or pod from a stable source — StatefulSet ordinal, an explicit env matrix, or the orchestrator's node identity. Log the resolved worker ID at boot and expose it on a health endpoint so a bad rollout is visible in minutes rather than discovered as duplicate-key errors weeks later.

Add a separate public identifier now, not later. Snowflake IDs leak creation timestamps and, at default settings, node identity. Any user-visible ID — URLs, API payloads, share links, support screenshots — should be a public_id (ULID/UUIDv7), decided in the same conversation as this rollout.

Casting and API output. Set protected $keyType = 'string'; if the trait doesn't already. Laravel merges the key type into getCasts(), so $model->id and toArray()/JSON output become strings — which is what you want, because a 19-digit ID exceeds JavaScript's Number.MAX_SAFE_INTEGER and will silently lose precision in the browser.

Tests. Keep factories free of id. For deterministic tests, bind a fake generator in the container rather than asserting on generated values:

$this->app->bind(PrimaryKeyGenerator::class, fn () => new class {
    public function generate(): string { return '900000000000000001'; }
});

Gotchas and Tips

$incrementing is the blocker, not the config. If Eloquent thinks the model auto-increments, performInsert() calls insertAndSetId(), which overwrites your generated ID with lastInsertId(). Confirm getIncrementing() is false and getKeyType() is 'string'. If the trait already declares these properties, redeclaring them in your model with a different value is a PHP fatal error — check the trait first, then declare only what's missing.

Schema type is a blocking pre-flight check. The id column must be BIGINT UNSIGNED and every foreign key referencing it must match. A signed/unsigned mismatch in MySQL surfaces as a foreign-key constraint failure at migration or insert time. This is a migration project for existing tables, not a config change.

Config caching will bite you in containers. php artisan config:cache bakes env() values into bootstrap/cache/config.php. If that runs at image build time, every pod ships the same SNOWFLAKE_WORKER_ID and you get silent collisions. Either run config:clear + config:cache at container start, or don't cache config at all in clusters where worker IDs are injected per pod.

The env var name for the file lock directory is SNOWFLAKE_File_LOCK_DIRECTORY — mixed case, straight from the README. .env keys are case-sensitive on Linux; a "corrected" SNOWFLAKE_FILE_LOCK_DIRECTORY silently won't apply. Also note v1.2.0 creates the default directory through Laravel's filesystem service — read-only container filesystems will fail here, so pick FileLockResolver deliberately or override the path.

Sequence resolvers do different things. LaravelSequenceResolver (default) keeps per-millisecond sequence state in your cache store — a per-node file/array cache is fine only because the worker ID is part of the ID. Distinct worker IDs are what actually protect you; the resolver protects the same-worker-same-millisecond case. FileLockResolver needs a lock path every process can reach, which usually means a shared filesystem across pods. Choose with the deployment topology in mind, not the default.

A misconfigured worker ID fails silently. Two nodes sharing an ID in the same millisecond produce a duplicate primary key — an insert error at best, a hard-to-reproduce one at worst. There's no validation in the package. Make "unique worker ID per process" an explicit deploy checklist item.

Clock skew. Snowflake depends on wall-clock time. NTP stepping backwards can produce non-monotonic or duplicate IDs. Configure slew rather than step on hosts running this, and treat clock sync as a correctness requirement, not hygiene.

Route binding, validation, and comparisons. whereNumber() route constraints and the integer validation rule will reject numeric-string IDs. Use string/numeric. And remember IDs coming from raw DB::table() queries may be int while $model->id is string — === comparisons across those two sources will fail.

Anything that assumes small IDs breaks. Fixtures with id => 1, seeders using Model::insert(), assertDatabaseHas('orders', ['id' => 1]), and insertGetId() on the query builder all need auditing. latest('id') and cursorPaginate() get better — Snowflake IDs are roughly time-ordered and make excellent keyset-pagination cursors.

If you override bootSnowflake() on a model, you silently disable ID generation — trait boot methods are overridable. Same for a snowflake() method with an incompatible signature; the trait requires : bool.

Don't mix with HasUuids/HasUlids or a model whose $primaryKey isn't id. Verify the trait writes to getKeyName() before relying on a custom primary key name.

Extension points worth knowing: the PrimaryKeyGenerator binding (swap for tests or custom algorithms) and snowflake(): bool per model. That's the entire integration surface — nothing else to learn, and nothing else to break.

Upgrading to 1.2.x changes write semantics. Pre-1.2 behavior hooked saving; anything that relied on an ID being regenerated during an update will now keep its original ID. That's the correct behavior, but if you have code that save()s a cloned record and expects a fresh ID, switch it to replicate() (which fires creating) and test it.

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.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk