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.
Illuminate\Contracts\Container\BindingResolutionException compatibility). Ideal for:
UuidInterface/UlidInterface as value objects in entities (e.g., User::id() returns UuidV7).Symfony\Component\Serializer).UUIDv7 enables chronological event replay without manual timestamps.UUID, MySQL BINARY(16)), string storage (RFC 4122/9562), and BASE_58 for compactness. Avoids vendor lock-in.UidFactoryInterface to UuidV7Factory (default) or UlidFactory in AppServiceProvider.protected $incrementing = true with protected $keyType = 'string' and cast id to UuidInterface.Uuid::isValid() / Ulid::isValid(), or Laravel’s UuidValidator (via symfony/validator). v8.1.8 tightened Ulid::isValid() — base-58 inputs exceeding 2^128 that were previously accepted now correctly fail.RFC_9562 (camelCase) or BASE_58 for compact payloads.^8.1.9 rather than ^8.1.8 or ^8.0.9. The 8.0 → 8.1 minor boundary carries only correctness fixes per the published changelog; treat adoption as low-risk patch-level. There is no reason to remain on 8.1.8 — v8.1.9 supersedes it with no trade-off.v8.1.9, run full test suite, re-run ID validation and ordering regression suites plus a pre-1970 timestamp fixture check (see Operational Impact).Ramsey\Uuid\Uuid with Symfony\Uid\Uuid in new features.UuidInterface to existing models via trait or abstract class.ALTER TABLE users MODIFY id BINARY(16)).Str::uuid()) with UidFactory.| Risk Area | Mitigation Strategy | Impact Assessment |
|---|---|---|
| Backward Compatibility | Use abstract base classes (e.g., AbstractUuidModel) to wrap legacy IDs during migration. v8.1.9 introduces no deprecations or signature changes. |
Low |
| Performance Overhead | Benchmark UuidV7 vs. Ulid generation (both <100ns on x64). Binary storage in DB reduces I/O by 20–30%. v8.1.9 has no measurable perf delta. |
Negligible |
| Clock Skew (UUIDv7) | Fixed in v8.1.8 (bug #66246): UuidV7 ordering now holds when the system clock goes backwards. Remaining exposure is cross-host skew during multi-region generation — retain NTP-synchronized clocks or use ULIDs (monotonic) where strict global ordering matters. |
Low (was Medium) |
| Pre-1970 Time-Based Derivation | Fixed in v8.1.9 (bug #66834): time-based UUIDs (v1/v6/v7) built from a date before 1970 with a fractional second previously produced an incorrect embedded timestamp. Only relevant if you generate IDs from supplied/backdated dates (archival imports, historical record backfills, legacy system replays) rather than from now(). Audit and re-derive any such IDs written under 8.1.8. |
Low (Medium if backdating) |
| Validation Tightening (Ulid) | v8.1.8 (bug #66253): Ulid::isValid() no longer accepts base-58 strings above 2^128. Audit inbound payloads and any lax validation gates; backfill/repair any persisted IDs that only passed under the old behaviour. |
Medium (one-time audit) |
| Mixed-Type Comparison | v8.1.8 (bug #66247): comparing a Uuid with a Ulid now behaves correctly. Audit dedupe/merge/sort logic in any environment where both types coexist (i.e. during phased migration). |
Low–Medium (audit) |
| Deterministic Test Fixtures | IDs derived from pre-1970 or clock-regressed timestamps may differ from prior versions. MockUuidFactory fixtures are unaffected; only hardcoded “expected generated value” assertions need review. |
Low |
| Testing Complexity | Use MockUuidFactory for deterministic UUIDs in CI/CD. Add one fixture per time-based regression fix (clock rollback, pre-1970 fractional second). |
Low |
| Database Schema Changes | Binary storage (PostgreSQL/MySQL 8.0+) is recommended for performance. Fallback to CHAR(36) if needed. |
Medium (one-time cost) |
| Team Adoption | Provide Laravel-specific examples (e.g., UuidModel, UlidModel traits) and migration scripts. |
Low (if documented well) |
BINARY(16))? If not, will CHAR(36) suffice?/share/2J...)?now() (backfills, archival replay, import of legacy records)? If so, do any of those dates precede 1970-01-01 with fractional-second precision?Ulid::isValid()? If so, do any currently-accepted payloads exceed 2^128 in base-58 form?Uuid/Ulid collections (dedupe sets, merge scripts, reconciliation jobs)?MockUuidFactory)?| Laravel Component | Integration Strategy | Example Implementation |
|---|---|---|
| Eloquent Models | Replace $incrementing = true with $keyType = 'string' and cast id to UuidInterface. Use UuidModel trait for boilerplate. |
```phpuse Symfony\Uid\Uuid;use Symfony\Uid\AbstractUuid;class User extends Model { protected $keyType = 'string'; protected $casts = [ 'id' => Uuid::class, ]; public function getRouteKey(): string { return $this->id->toRfc9562(); }} |
| API Responses | Serialize to RFC_9562 (camelCase) or BASE_58 for compactness. Use Symfony\Component\Serializer or Laravel’s JsonSerializable. |
```phpreturn response()->json([ 'id' => $user->id->toRfc9562(), 'name' => $user->name,]); |
| Validation | Use Uuid::isValid() / Ulid::isValid(), or Laravel’s UuidValidator (via symfony/validator). On v8.1.8+, Ulid::isValid() rejects base-58 values above 2^128 — re-baseline any custom rules built on the old behaviour. |
```phpuse Symfony\Component\Validator\Constraints as Assert;class CreateUserRequest { #[Assert\Uuid] public string $externalId;} |
| Database Schema | Prefer BINARY(16) for UUIDs/ULIDs (PostgreSQL/MySQL 8.0+). Fallback to CHAR(36) if needed. |
```sqlALTER TABLE users MODIFY id BINARY(16);-- or --ALTER TABLE users MODIFY id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin; |
| Event Sourcing | Use UuidV7 for time-ordered events. Ordering is stable under clock regression (v8.1.8); backdated events now carry the correct embedded timestamp (v8.1.9). Store as binary in DB for efficiency. |
```phpclass OrderCreated { public UuidV7 $orderId; public Carbon $occurredOn;} |
| Backdated / Imported Records | If replaying historical data, derive IDs after upgrading to v8.1.9, or use MockUuidFactory-style deterministic derivation so the same source date always yields the same ID. Verify embedded timestamps round-trip. |
```php$id = UuidV7::fromDateTime( new \DateTimeImmutable('1965-04-11 12:00:00.250'));$id->getDateTime(); // assert correct |
| Mixed ID Handling | When Uuid and Ulid coexist (mid-migration), comparison semantics are correct as of v8.1.8. Still normalize to a single type at boundaries to avoid ambiguity. |
```php$a = Uuid::fromString($x);$b = Ulid::fromString($y);// convert before compare$a->equals(Uuid::fromString($b->toRfc9562())); |
| Testing | Use MockUuidFactory for deterministic UUIDs in CI/CD. Add regression coverage for Ulid validation bounds, clock-regression ordering, and pre-1970 fractional-second date derivation. |
```php$factory = new MockUuidFactory('123e4567-e89b-12d3-a456-426614174000');$user = new User();$user->id = $factory->generate(); |
symfony/uid to ^8.1.9; confirm no other Symfony packages are pinned below the 8.1 line.Uuid/Ulid comparisons, UUIDv7 ordering under simulated clock rollback, and time-based UUID derivation from a pre-1970 date with fractional seconds.fromDateTime / date-supplied generation call sites and confirm none run in a hot path requiring re-derivation.Ramsey\Uuid, custom logic, Str::uuid()).Ulid::isValid() call sites and any persisted base-58 IDs; reconcile against the tightened rule.UuidModel/UlidModel traits for new models.Ramsey\Uuid with Symfony\Uid in new features.UidFactory bindings.Ramsey\Uuid and legacy Str::uuid() usages.v8.1.9 is a single-fix patch on an active minor line — effectively a one-line correctness change with no surface area. Adoption cost is a composer bump plus a test run; merge risk is near zero.^8.1.9 and let the constraint float within the minor; avoid * so that intentional review happens at minor boundaries.8.0.x.@nicolas-grekas), which is a strong indicator of regression-test coverage on the time-based path going forward.UidFactoryInterface → UuidV7Factory) so support load doesn’t concentrate on one engineer.| Failure Mode | Trigger | Detection | Response |
|---|---|---|---|
| Validation regression (422s) | Endpoints using Ulid::isValid() receive base-58 values above 2^128 that previously passed |
Spike in 4xx on ingest endpoints post-deploy | Expected/correct behaviour; fix the client or reject the record. Pre-audit before upgrading to avoid surprise. |
| Persisted invalid ULIDs | Rows written while the loose validation was in effect | Data audit query on ULID columns; length/charset checks | Backfill or quarantine affected rows; do not relax validation to compensate. |
| Mixed compare/sort drift | Dedupe or reconciliation jobs comparing Uuid against Ulid |
Diff in dedupe counts, duplicate-key errors, reconciliation mismatches | Fixed in 8.1.8 (bug #66247); re-run affected jobs to confirm outputs converge. |
| UUIDv7 ordering surprise | Clock steps backwards on a generating host | Out-of-order event sequences, index hot spots | Fixed in 8.1.8 (bug #66246) within-process; cross-host skew still requires NTP or ULID. |
| Misdating on backdated IDs | Time-based UUID generated from a pre-1970 date with fractional seconds on v8.1.8 | Embedded timestamp round-trip mismatch; archival events sorting before their true date | Fixed in 8.1.9 (bug #66834). Re-derive affected IDs if immutability allows; otherwise record a correction mapping and exclude from ordering assumptions. |
| Test fixture breakage | Hardcoded expectations of generated IDs in clock-regression or pre-1970 date scenarios | CI failures scoped to ID-ordering/date tests | Move to MockUuidFactory fixtures; assert ordering and date properties, not literal values. |
| Partial upgrade | symfony/uid 8.1.x alongside other Symfony packages pinned to 8.0.x |
Composer resolution conflicts or runtime ClassNotFound |
Align the Symfony package set before upgrading; stage in a branch. |
Uuid/Ulid in production), allocate an extra half-day for a mixed-type comparison audit; this is the highest-value use of time in the upgrade. If the team imports historical data, allocate a similar half-day to confirm no IDs were derived from pre-1970 dates under 8.1.8.How can I help you explore Laravel packages today?