feat(infra): add HasUuidV7CreatedAt trait to derive created_at from UUIDv7

This commit is contained in:
Jérémy Munsch 2026-08-21 16:11:56 +02:00
parent 05af05e171
commit 6d63e68908
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
2 changed files with 144 additions and 0 deletions

View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Infra\Database\Concerns;
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
use Shared\Helpers\UuidHelper;
/** @mixin \Illuminate\Database\Eloquent\Model&\Illuminate\Database\Eloquent\Concerns\HasUuids */
trait HasUuidV7CreatedAt
{
public static function bootHasUuidV7CreatedAt(): void
{
static::creating(static function (Model $model): void {
if ($model->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));
}
}
}
});
}
}

View file

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Infra\Database\Concerns;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Infra\Casts\UtcDateTimeCast;
use Infra\Database\Concerns\HasUuidV7CreatedAt;
use Ramsey\Uuid\Uuid;
use Tests\TestCase;
covers(HasUuidV7CreatedAt::class);
uses(TestCase::class);
describe('HasUuidV7CreatedAt Trait Unit Tests', function (): void {
beforeEach(function (): void {
Schema::create('test_uuid_models', function (Blueprint $table): void {
$table->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');
});
});