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

Uid Laravel Package

symfony/uid

Symfony UID provides an object-oriented API for generating and representing unique identifiers, with implementations for ULIDs and UUID v1 and v3 through v8 that work on both 32-bit and 64-bit CPUs.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require symfony/uid:^8.1
    

    Pin at least 8.1.9. That release contains four bug fixes (UuidV7 clock-regression ordering, Uuid↔Ulid comparison, Ulid::isValid() overflow, and pre-1970 fractional-second date decoding) that change observable behaviour, so anything below it can produce silently wrong sorts, validations, and extracted timestamps.

  2. First Use Case — a time-ordered primary key:

    use Symfony\Component\Uid\Uuid;
    
    $id = Uuid::v7();
    
    (string) $id;        // "0191f2c3-7a4b-7c9d-8e0f-1a2b3c4d5e6f"  (RFC 9562, canonical)
    $id->toBase58();     // compact, URL-safe
    $id->toBinary();     // 16 raw bytes for BINARY(16) columns
    $id->getDateTime();  // \DateTimeImmutable extracted from the embedded timestamp
    
  3. Where to Look First:

    • Component docs — covers v1/v4/v6/v7/v8, ULID, and the toBase32()/toBase58()/toBinary() encoders.
    • Symfony\Component\Uid\Uuid for the static factories (::v4(), ::v7(), ::v5()), Symfony\Component\Uid\Factory\UuidV7Factory if you need an injectable, seeded generator.
    • Symfony\Component\Uid\AbstractUid for equals(), compare(), and the conversion methods every UID type shares.

Implementation Patterns

Core Workflows

1. Eloquent Integration

There is no UuidTrait in this package — that belongs to spatie/laravel-uuid. On Laravel 11+ use the framework's HasUniqueStringIds hook and let Symfony generate the value:

use Illuminate\Database\Eloquent\Concerns\HasUniqueStringIds;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\Uid\Uuid;

class Post extends Model
{
    use HasUniqueStringIds;

    public function newUniqueId(): string
    {
        return Uuid::v7()->toRfc4122();
    }

    public function uniqueIds(): array
    {
        return ['id'];
    }
}

This keeps $keyType/$incrementing handling inside Laravel while the ID format stays under your control (toBase58() here also works if the column is wide enough and you never need to hand the value to an external UUID consumer).

2. Time-Ordered IDs You Can Actually Sort On

Since 8.1.8, UuidV7 guarantees monotonically increasing values even when the system clock steps backwards (NTP correction, VM restore, container drift). That makes v7 safe for ORDER BY id, cursor pagination, and "newest first" feeds without a secondary created_at tiebreaker:

$events = Event::query()->latest('id')->cursorPaginate(50);

Before 8.1.8 a backwards clock jump could emit a v7 smaller than one already written — rows then sorted "into the past". If you shipped pre-8.1.8 v7 keys, upgrade first and spot-check ordering on any host that has had clock adjustments; you can't repair the old values, only stop new ones from being wrong.

ULID remains the alternative when you want the same time-ordering in a 26-char Crockford base32 string, at the cost of RFC compliance.

3. URL-Friendly Identifiers

$id = Uuid::v7();
route('share', ['id' => $id->toBase58()]);   // 22 chars vs 36 for canonical hex

Resolve on the way back in with Uuid::fromBase58($value); it throws on malformed input, so wrap it in a Form Request rule rather than a controller.

4. Deterministic Tests

The timestamp is inside the value, so assert on it instead of comparing whole strings:

$this->travelTo('2024-01-01 00:00:00');

$id = Uuid::v7();

$this->assertSame('2024-01-01 00:00:00', $id->getDateTime()->format('Y-m-d H:i:s'));

For a fully fixed sequence, inject Symfony's MockUuidV7Factory (7.3+) into the container in the test's setUp() and let the factory return predetermined values.

If any test travels before 1970 with a fractional second (travelTo('1969-12-31 23:59:59.5')), re-baseline its getDateTime() expectations on 8.1.9 — see the date-decoding fix below.

5. Database Storage

Schema::create('posts', function (Blueprint $table) {
    $table->uuid('id')->primary();       // Postgres: native uuid; MySQL: char(36)
    // or, 16 bytes on MySQL 8+:
    // $table->binary('id', 16)->primary();
    $table->string('title');
    $table->timestamps();
});

On Postgres keep the native uuid type — it's 16 bytes internally and sorts/indexes correctly. On MySQL, BINARY(16) halves storage and speeds up index scans, but every read/write needs conversion (Uuid::fromBinary() / ->toBinary()), which Eloquent will not do for you.

Integration Tips

  1. Bind the factory, not the value:

    use Symfony\Component\Uid\Factory\UuidV7Factory;
    
    public function register(): void
    {
        $this->app->singleton(UuidV7Factory::class, fn () => new UuidV7Factory());
    }
    

    The v7 factory carries internal state (last timestamp/counter) to enforce monotonicity. Reusing one instance is both faster and the mechanism that keeps ordering tight under bursts.

  2. API serialisation — AbstractUid implements Stringable, so (string) $uuid and $uuid->toRfc4122() both emit canonical lowercase hex. Don't reach for UidNormalizer unless you're already in a Symfony Serializer pipeline; Laravel's JSON encoding handles the string cast fine.

  3. Validation:

    use Symfony\Component\Uid\Uuid;
    
    'uuid' => ['required', function ($attribute, $value, $fail) {
        if (!Uuid::isValid($value)) {
            $fail("The {$attribute} must be a valid UUID.");
        }
    }],
    

    Uuid::isValid() is stricter than Laravel's uuid rule in one useful way: it accepts any RFC 9562 version but rejects look-alikes. Use Ulid::isValid() for ULIDs — and see the 8.1.8 fix below, because its acceptance surface just shrank.

  4. Performance:

    • Generate in bulk from a single factory instance; per-call cost is a few microseconds, so 10k IDs is tens of milliseconds, not hundreds.
    • Store binary on MySQL only if you also benchmark the encode/decode overhead in your hottest read path — for most apps uuid/char(36) is fast enough and far less error-prone.

Gotchas and Tips

Changes in 8.1.8 / 8.1.9 that alter behaviour

  1. UuidV7 ordering when the clock goes backwards (#66246, 8.1.8) — previously a clock regression could emit a v7 lower than a previously issued one, breaking monotonic assumptions. Now ordering holds. Consequence: if you wrote v7 keys on pre-8.1.8 hosts and rely on ORDER BY id for recency, older rows are already misordered. New rows won't fix the gap; re-sort by created_at when accuracy matters for historical data.

  2. Comparing a Uuid with a Ulid (#66247, 8.1.8) — cross-type comparison used to give inconsistent results. It is now well-defined, and a Uuid never equals a Ulid (different 128-bit namespaces, even for identical bytes). If any test or guard clause compared the two loosely, it will change behaviour. Prefer ->equals() over ==, and ->compare() when you need an ordering — both are type-aware.

  3. Ulid::isValid() and oversized base-58 (#66253, 8.1.8) — base-58 strings decoding above 2^128 previously passed validation. They no longer do. If you accept ULIDs from clients/downstream systems that were validated pre-upgrade, some values may now be rejected. Rejecting is correct (they could never round-trip through Ulid::fromString()), but audit any validation you added to paper over the asymmetry.

  4. Dates of time-based UUIDs before 1970 with a fractional second (#66834, 8.1.9) — getDateTime() on a v1/v6/v7 value whose instant lands before the Unix epoch and carries a non-zero sub-second part could return the wrong date (the fractional component was decoded against a signed timestamp, shifting the result). Fixed in 8.1.9. This only affects values you construct for pre-1970 instants — backfilled historical data, seeders, and travelTo() tests — never freshly generated IDs. If you asserted on getDateTime() for such values on 8.1.8 or earlier, those expectations were encoding the bug: re-derive them after upgrading, and treat any stored pre-epoch timestamp derived from getDateTime() as suspect. The stored UUID bytes are unaffected; only the decode side changed, so no data migration is needed — just don't trust previously computed dates.

General pitfalls

  1. Don't mix UID libraries. symfony/uid, ramsey/uuid, and spatie/laravel-uuid all coexist happily at the type level but not at the semantics level: Laravel's built-in HasUuids trait uses Ramsey's ordered UUID, so a codebase can end up generating two different "v7-looking" formats. Pick one generator and route all ID creation through it.

  2. Binary columns need explicit accessors. Eloquent will happily return raw 16-byte blobs from a BINARY(16) column. Wrap them:

    protected function id(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => Uuid::fromBinary($value)->toRfc4122(),
            set: fn (string $value) => Uuid::fromString($value)->toBinary(),
        );
    }
    

    Route-model binding, where('id', $uuid) and eager loads all need the same treatment or you'll get silent zero-result queries.

  3. Normalise before comparing at the database level. Symfony emits canonical lowercase, but IDs arriving from external systems may not be. Compare on toBinary() (or lowercase both sides) rather than raw strings — Postgres uuid does this for you, char(36) does not.

  4. v7 is time-ordered, not tamper-proof. The embedded timestamp is readable by anyone. Use v4 for anything resembling a token, and never treat v7's ordering as an authorization or rate-limiting primitive.

  5. No ext-ds dependency. symfony/uid is pure PHP (ext-random for entropy); you don't need to install extensions.

Debugging Tips

  1. Malformed input: Uuid::fromString() throws \InvalidArgumentException with the offending value — far more useful in a stack trace than isValid() returning false. Validate with isValid() in requests, parse with fromString() in domain code.

  2. Suspicious ordering: pull the timestamp out and confirm it matches expectations.

    $id = Uuid::fromString($value);
    dump($id->getDateTime(), $id->toBinary());
    

    If two IDs with the same millisecond sort unexpectedly, check the package version first — that's exactly what #66246 fixed.

  3. Suspicious dates on imported or backdated IDs: if a pre-1970 value reports a time that is off by a fraction of a second (or lands on the wrong second entirely), confirm you're on 8.1.9 before chasing your own conversion code — #66834 was a decode-side bug, so re-running the extraction on the upgraded package is the fastest check.

  4. Bulk generation benchmarks: measure with a single reused UuidV7Factory rather than Uuid::v7() in a loop; the static factory creates a fresh instance per call and the difference shows up above ~50k IDs.

Extension Points

  1. Custom UID types: extend Symfony\Component\Uid\AbstractUid and implement toBinary(), __toString(), and the static fromString(). This is the cleanest way to introduce a domain-specific prefixed identifier (e.g. user_ + base58) that still interoperates with equals()/compare().

  2. Name-based IDs (v5): Uuid::v5(Uuid $namespace, string $name) is deterministic by design — useful for idempotency keys where you want the same ID for the same logical input without a lookup table. This is the supported alternative to mocking the generator.

  3. Injectable clock: UuidV7Factory accepts a TimeBasedUidInterface, which is the seam to use when you need reproducible IDs across a whole test suite without touching static factories.

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.
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator