Skip to content

Commit 72f3828

Browse files
authored
UUIDv7 (#23)
1 parent 3dc2b0d commit 72f3828

File tree

3 files changed

+68
-4
lines changed

3 files changed

+68
-4
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# uuid-php
55

6-
A small PHP class for generating [RFC 4122](http://tools.ietf.org/html/rfc4122) version 3, 4, 5, and 6 ([draft](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02)) universally unique identifiers (UUID).
6+
A small PHP class for generating [RFC 4122](http://tools.ietf.org/html/rfc4122) version 3, 4, and 5 universally unique identifiers (UUID). Additionally supports [draft](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02) versions 6 and 7.
77

88
If all you want is a unique ID, you should call `uuid4()`.
99

@@ -25,7 +25,7 @@ echo uuid4();
2525

2626
## Installation
2727

28-
If you need comparison tools or sortable identifiers like in version 6, you might find this small and fast package useful. It doesn't require any other dependencies.
28+
If you need comparison tools or sortable identifiers like in versions 6 and 7, you might find this small and fast package useful. It doesn't require any other dependencies.
2929

3030
```bash
3131
composer require oittaa/uuid
@@ -58,6 +58,12 @@ echo $uuid6_first . "\n"; // e.g. 1ebacf4f-a4a8-68ee-b4ec-618c14d005d5
5858
$uuid6_second = UUID::uuid6();
5959
var_dump($uuid6_first < $uuid6_second); // bool(true)
6060

61+
// Generate a version 7 (lexicographically sortable) UUID
62+
$uuid7_first = UUID::uuid7();
63+
echo $uuid7_first . "\n"; // e.g. 061a3d43-61d0-7cf4-bfce-753dadab55e1
64+
$uuid7_second = UUID::uuid7();
65+
var_dump($uuid7_first < $uuid7_second); // bool(true)
66+
6167
// Test if a given string is a valid UUID
6268
$isvalid = UUID::isValid('11a38b9a-b3da-360f-9353-a5a725514269');
6369
var_dump($isvalid); // bool(true)

src/UUID.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,30 @@ public static function uuid6()
176176
return self::uuidFromHash($hash, 6);
177177
}
178178

179+
/**
180+
* Generate a version 7 UUID. A v7 UUID is lexicographically sortable and is
181+
* designed to encode a Unix timestamp with arbitrary sub-second precision.
182+
*
183+
* @return string The string standard representation of the UUID
184+
*/
185+
public static function uuid7()
186+
{
187+
$time = microtime(false);
188+
$unixts = substr($time, 11);
189+
$subsec = substr($time, 2, 7);
190+
$unixts = str_pad(dechex(intval($unixts, 10)), 9, '0', \STR_PAD_LEFT);
191+
$subsec = str_pad(dechex(intval($subsec, 10)), 6, '0', \STR_PAD_LEFT);
192+
$time = sprintf(
193+
'%09s%03s7%03s',
194+
$unixts,
195+
substr($subsec, 0, 3),
196+
substr($subsec, -3)
197+
);
198+
$bytes = random_bytes(8);
199+
$hash = $time . bin2hex($bytes);
200+
return self::uuidFromHash($hash, 7);
201+
}
202+
179203
/**
180204
* Check if a string is a valid UUID.
181205
*
@@ -276,4 +300,12 @@ public static function v6()
276300
{
277301
return self::uuid6();
278302
}
303+
/**
304+
* @see UUID::uuid7() Alias
305+
* @return string
306+
*/
307+
public static function v7()
308+
{
309+
return self::uuid7();
310+
}
279311
}

tests/UuidTest.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function testCanGenerateValidVersion3()
2323
public function testCanGenerateValidVersion4()
2424
{
2525
$uuid1 = UUID::uuid4();
26-
for ($x = 0; $x < 10; $x++) {
26+
for ($x = 0; $x < 1000; $x++) {
2727
$this->assertMatchesRegularExpression(
2828
'/^[0-9a-f]{8}\-[0-9a-f]{4}\-4[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}$/',
2929
$uuid1
@@ -48,7 +48,7 @@ public function testCanGenerateValidVersion5()
4848
public function testCanGenerateValidVersion6()
4949
{
5050
$uuid1 = UUID::uuid6();
51-
for ($x = 0; $x < 10; $x++) {
51+
for ($x = 0; $x < 1000; $x++) {
5252
$this->assertMatchesRegularExpression(
5353
'/^[0-9a-f]{8}\-[0-9a-f]{4}\-6[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}$/',
5454
$uuid1
@@ -67,6 +67,28 @@ public function testCanGenerateValidVersion6()
6767
}
6868
}
6969

70+
public function testCanGenerateValidVersion7()
71+
{
72+
$uuid1 = UUID::uuid7();
73+
for ($x = 0; $x < 1000; $x++) {
74+
$this->assertMatchesRegularExpression(
75+
'/^[0-9a-f]{8}\-[0-9a-f]{4}\-7[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}$/',
76+
$uuid1
77+
);
78+
usleep(1);
79+
$uuid2 = UUID::uuid7();
80+
$this->assertGreaterThan(
81+
$uuid1,
82+
$uuid2
83+
);
84+
$this->assertLessThan(
85+
0,
86+
UUID::cmp($uuid1, $uuid2)
87+
);
88+
$uuid1 = $uuid2;
89+
}
90+
}
91+
7092
public function testCannotBeCreatedFromInvalidNamespace()
7193
{
7294
$this->expectException(\InvalidArgumentException::class);
@@ -155,5 +177,9 @@ public function testCanUseAliases()
155177
6,
156178
UUID::getVersion(UUID::v6())
157179
);
180+
$this->assertSame(
181+
7,
182+
UUID::getVersion(UUID::v7())
183+
);
158184
}
159185
}

0 commit comments

Comments
 (0)