Skip to content

Commit 1d9474f

Browse files
committed
Add StringUuidV4::generate implementation with RFC 4122 UUID v4 generation and tests
- Implemented `StringUuidV4::generate` method to produce valid, lowercase UUID v4 values as per RFC 4122 standard. - Introduced `GenerateInterface` for reusable value generation. - Enhanced unit tests to verify UUID validity, uniqueness, and proper formatting. - Updated `psalmTest.php` with `StringUuidV4::generate` demonstration.
1 parent 3121516 commit 1d9474f

File tree

4 files changed

+66
-1
lines changed

4 files changed

+66
-1
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpTypedValues\Code\Contract;
6+
7+
/**
8+
* @psalm-immutable
9+
*/
10+
interface GenerateInterface
11+
{
12+
public static function generate(): static;
13+
}

src/String/StringUuidV4.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44

55
namespace PhpTypedValues\String;
66

7+
use PhpTypedValues\Code\Contract\GenerateInterface;
78
use PhpTypedValues\Code\Exception\StringTypeException;
89
use PhpTypedValues\Code\String\StrType;
10+
use Random\RandomException;
911

12+
use function chr;
13+
use function ord;
1014
use function preg_match;
1115
use function sprintf;
1216
use function strtolower;
@@ -16,7 +20,7 @@
1620
*
1721
* @psalm-immutable
1822
*/
19-
readonly class StringUuidV4 extends StrType
23+
readonly class StringUuidV4 extends StrType implements GenerateInterface
2024
{
2125
/** @var non-empty-string */
2226
protected string $value;
@@ -56,4 +60,33 @@ public function value(): string
5660
{
5761
return $this->value;
5862
}
63+
64+
/**
65+
* @throws RandomException
66+
* @throws StringTypeException
67+
*/
68+
public static function generate(): static
69+
{
70+
// UUID v4: 16 random bytes, with version and variant bits set as per RFC 4122
71+
$bytes = random_bytes(16);
72+
73+
// Set version to 4 (0100xxxx)
74+
$bytes[6] = chr((ord($bytes[6]) & 0x0F) | 0x40);
75+
76+
// Set variant to RFC 4122 (10xxxxxx)
77+
$bytes[8] = chr((ord($bytes[8]) & 0x3F) | 0x80);
78+
79+
$hex = bin2hex($bytes);
80+
81+
$uuid = sprintf(
82+
'%s-%s-%s-%s-%s',
83+
substr($hex, 0, 8),
84+
substr($hex, 8, 4),
85+
substr($hex, 12, 4),
86+
substr($hex, 16, 4),
87+
substr($hex, 20, 12)
88+
);
89+
90+
return new static($uuid);
91+
}
5992
}

src/psalmTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
use PhpTypedValues\String\Alias\NonEmptyStr;
2626
use PhpTypedValues\String\StringNonEmpty;
2727
use PhpTypedValues\String\StringStandard;
28+
use PhpTypedValues\String\StringUuidV4;
29+
use PhpTypedValues\String\StringUuidV7;
2830

2931
/**
3032
* Integer.
@@ -47,6 +49,8 @@
4749

4850
echo StringStandard::fromString('hi')->toString() . \PHP_EOL;
4951
echo NonEmptyStr::fromString('hi')->toString() . \PHP_EOL;
52+
echo StringUuidV7::generate()->toString() . \PHP_EOL;
53+
echo StringUuidV4::generate()->toString() . \PHP_EOL;
5054

5155
/**
5256
* Float.

tests/Unit/String/StringUuidV4Test.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,18 @@
4343
expect(fn() => StringUuidV4::fromString($badChar))
4444
->toThrow(StringTypeException::class, 'Expected UUID v4 (xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx), got "' . $badChar . '"');
4545
});
46+
47+
it('generate produces a valid lowercase UUID v4 and different values across calls', function (): void {
48+
$a = StringUuidV4::generate();
49+
$b = StringUuidV4::generate();
50+
51+
$regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/';
52+
53+
expect($a->toString())->toMatch($regex)
54+
->and($a->value())->toBe($a->toString())
55+
->and($a->toString())->toBe(strtolower($a->toString()))
56+
// Two subsequently generated UUIDs should not be equal with overwhelmingly high probability
57+
->and($a->toString())->not->toBe($b->toString())
58+
->and($b->toString())->toMatch($regex)
59+
->and($b->toString())->toBe(strtolower($b->toString()));
60+
});

0 commit comments

Comments
 (0)