From 6d63e68908820f4c2bc61c84b3c92f568213cdd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 21 Aug 2026 16:11:56 +0200 Subject: [PATCH] feat(infra): add HasUuidV7CreatedAt trait to derive created_at from UUIDv7 --- .../Database/Concerns/HasUuidV7CreatedAt.php | 34 ++++++ .../Concerns/HasUuidV7CreatedAtTest.php | 110 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 src/Infra/Database/Concerns/HasUuidV7CreatedAt.php create mode 100644 tests/Unit/Infra/Database/Concerns/HasUuidV7CreatedAtTest.php diff --git a/src/Infra/Database/Concerns/HasUuidV7CreatedAt.php b/src/Infra/Database/Concerns/HasUuidV7CreatedAt.php new file mode 100644 index 0000000..45b827c --- /dev/null +++ b/src/Infra/Database/Concerns/HasUuidV7CreatedAt.php @@ -0,0 +1,34 @@ +getAttribute('created_at') === null) { + $key = $model->getKey(); + + if (empty($key)) { + $key = $model->newUniqueId(); + $model->setAttribute($model->getKeyName(), $key); + } + + if (! empty($key) && (is_string($key) || $key instanceof \Stringable) && Uuid::isValid((string) $key)) { + $uuid = Uuid::fromString((string) $key); + if (UuidHelper::isV7($uuid)) { + $model->setAttribute('created_at', UuidHelper::toDateTime($uuid)); + } + } + } + }); + } +} diff --git a/tests/Unit/Infra/Database/Concerns/HasUuidV7CreatedAtTest.php b/tests/Unit/Infra/Database/Concerns/HasUuidV7CreatedAtTest.php new file mode 100644 index 0000000..bb6db29 --- /dev/null +++ b/tests/Unit/Infra/Database/Concerns/HasUuidV7CreatedAtTest.php @@ -0,0 +1,110 @@ +uuid('id')->primary(); + $table->string('name'); + $table->dateTime('created_at'); + }); + }); + + afterEach(function (): void { + Schema::dropIfExists('test_uuid_models'); + }); + + it('automatically populates created_at from UUIDv7 upon model creation', function (): void { + // GIVEN + $expectedDate = new \DateTimeImmutable('2026-08-21 11:30:00 UTC'); + $uuid7 = Uuid::uuid7($expectedDate)->toString(); + + $testModelClass = new class () extends Model + { + use HasUuidV7CreatedAt; + + protected $table = 'test_uuid_models'; + + public $timestamps = false; + + public $incrementing = false; + + protected $keyType = 'string'; + + protected $fillable = ['id', 'name', 'created_at']; + + #[\Override] + protected function casts(): array + { + return [ + 'created_at' => UtcDateTimeCast::class, + ]; + } + }; + + // WHEN + $instance = $testModelClass::query()->create([ + 'id' => $uuid7, + 'name' => 'Test Item', + ]); + + // THEN + expect($instance->created_at)->toBeInstanceOf(\DateTimeImmutable::class) + ->and($instance->created_at->format('Y-m-d H:i:s'))->toBe('2026-08-21 11:30:00'); + }); + + it('does not overwrite explicitly provided created_at', function (): void { + // GIVEN + $uuidDate = new \DateTimeImmutable('2026-08-21 11:30:00 UTC'); + $explicitDate = new \DateTimeImmutable('2026-01-01 00:00:00 UTC'); + $uuid7 = Uuid::uuid7($uuidDate)->toString(); + + $testModelClass = new class () extends Model + { + use HasUuidV7CreatedAt; + + protected $table = 'test_uuid_models'; + + public $timestamps = false; + + public $incrementing = false; + + protected $keyType = 'string'; + + protected $fillable = ['id', 'name', 'created_at']; + + #[\Override] + protected function casts(): array + { + return [ + 'created_at' => UtcDateTimeCast::class, + ]; + } + }; + + // WHEN + $instance = $testModelClass::query()->create([ + 'id' => $uuid7, + 'name' => 'Test Item', + 'created_at' => $explicitDate, + ]); + + // THEN + expect($instance->created_at->format('Y-m-d H:i:s'))->toBe('2026-01-01 00:00:00'); + }); +});