fab2s/souuid
fab2s/souuid generates sortable, time-ordered UUIDs for Laravel and PHP. These UUID-compatible IDs sort chronologically, improving database index locality and making them practical, efficient primary keys.
Install with Composer:
composer require fab2s/souuid
The package is deliberately small — one generator class plus (likely) a value object. Before writing any app code, open src/ and the README and confirm three things: the exact class/method name for generation (e.g. Souuid::generate()), the return type (raw string vs. an object with __toString()), and the byte layout of the produced UUID (how many bits are timestamp, random, counter). That last point determines whether you store it as char(36), char(32), or binary(16).
Minimal first use — generate an ordered ID and use it as a string primary key:
use Fab2s\Souuid\Souuid; // confirm name in src/
$id = (string) Souuid::generate();
// e.g. used as: $post->id = $id;
The first real use case is a new table where you want globally unique IDs that still sort chronologically — replacing $table->id() because you need IDs generated client-side (before insert), stable across shards, or safe to expose in URLs, while keeping insert locality good enough that your B-tree index doesn't fragment the way random UUIDv4 does.
If you just need to verify behaviour, generate a loop of 1,000 IDs and sort() them lexicographically alongside their creation order — they should match. That five-line sanity check tells you more about the package than the docs will.
Model trait. Wrap generation in a reusable trait so every model behaves identically:
trait HasSouuid
{
protected static function bootHasSouuid(): void
{
static::creating(function ($model) {
$model->{$model->getKeyName()} ??= (string) Souuid::generate();
});
}
public function getIncrementing(): bool { return false; }
public function getKeyType(): string { return 'string'; }
}
The ??= matters: it lets you inject a pre-generated ID (useful for outbox/event-sourcing patterns and for tests) without the trait overwriting it.
Migration. Match the column to your storage choice. For string storage:
$table->char('id', 36)->primary();
If the package emits a compact/hex form, size the column to the actual output and keep the collation binary (->collation('utf8mb4_bin') on MySQL) so ordering is byte-wise and not case-folded. Ordering correctness is the whole point of the package — a case-insensitive collation can silently reorder IDs.
Route model binding works out of the box once getKeyType() returns string; no getRouteKeyName() override needed. Guard against enumeration by keeping authorization policies on the model rather than relying on ID opacity.
Relationships and foreign keys need the same column definition on the child table. If you're using foreignId() elsewhere in the codebase, use foreignUuid()-equivalent (char(36)) here instead, and add the index explicitly — Laravel won't infer it for a non-standard type.
Bulk inserts should call the generator once per row before the query builder runs; skip Eloquent events for throughput but retain ordering by generating in loop order (the sortable prefix does the rest).
Where to use it vs. Str::uuid(): use this package for anything that becomes a primary key or a sort key (posts, orders, ledger entries, job payload IDs you'll ORDER BY). Keep plain UUIDv4 for opaque tokens and correlation IDs where ordering is irrelevant — the ordered prefix leaks a coarse creation timestamp.
HasUuids/HasUniqueStringIds traits emit v4 (unordered) and will happily collide conceptually with this package's format. Pick one strategy per table and remove the other trait.utf8mb4_unicode_ci column will sort hex UUIDs differently than you expect. Test ORDER BY id against insertion order after your first real migration, not in a unit test with SQLite.src/ whether a monotonic counter or sub-millisecond precision is used. If not, generating many IDs in a tight loop within one millisecond may produce ties or non-monotonic output — benchmark your actual seeding/bulk-import path rather than assuming.setTestNow) if you assert on sortability, and never assert on exact ID values. Assert on format, length, and monotonicity instead.__toString() call site consistently — an object assigned to $model->id will fail char(36) inserts in confusing ways.IdGenerator interface, bind this package's implementation behind it, and swap later without touching models. That indirection is the highest-value thing you can add on day one.composer.json and add a test that asserts the ID format, so a silent format change on upgrade fails CI rather than production inserts.How can I help you explore Laravel packages today?