Skip to content

Commit 4f4deb1

Browse files
committed
feat: add UUIDv7 documentation and customization
1 parent 28f509b commit 4f4deb1

17 files changed

+348
-31
lines changed

docs/customize/ordered-time-codec.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
Ordered-time Codec
55
==================
66

7-
.. hint::
7+
.. attention::
88

9-
:ref:`Version 6, reordered time UUIDs <rfc4122.version6>` are a
10-
new version of UUID that take the place of ordered-time UUIDs.
9+
:ref:`Version 6, reordered time UUIDs <rfc4122.version6>` are a new version
10+
of UUID that eliminate the need for the ordered-time codec. If you aren't
11+
currently using the ordered-time codec, and you need time-based, sortable
12+
UUIDs, consider using version 6 UUIDs.
1113

1214
UUIDs arrange their bytes according to the standard recommended by `RFC 4122`_.
1315
Unfortunately, this means the bytes aren't in an arrangement that supports

docs/customize/timestamp-first-comb-codec.rst

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
Timestamp-first COMB Codec
55
==========================
66

7+
.. attention::
8+
9+
:ref:`Version 7, Unix Epoch time UUIDs <rfc4122.version7>` are a new version
10+
of UUID that eliminate the need for the timestamp-first COMB codec. If you
11+
aren't currently using the timestamp-first COMB codec, and you need
12+
time-based, sortable UUIDs, consider using version 7 UUIDs.
13+
714
:ref:`Version 4, random UUIDs <rfc4122.version4>` are doubly problematic when it
815
comes to sorting and storing to databases (see :ref:`database.order`), since
916
their values are random, and there is no timestamp associated with them that may

docs/database.rst

+11-12
Original file line numberDiff line numberDiff line change
@@ -229,29 +229,28 @@ small.
229229
Insertion Order and Sorting
230230
###########################
231231

232-
UUIDs are not *monotonically increasing*. Even time-based UUIDs are not. If
233-
using UUIDs as primary keys, the inserts will be random, and the data will be
232+
UUID versions 1, 2, 3, 4, and 5 are not *monotonically increasing*. If using
233+
these versions as primary keys, the inserts will be random, and the data will be
234234
scattered on disk (for InnoDB). Over time, as the database size grows, lookups
235235
will become slower and slower.
236236

237-
.. note::
237+
.. tip::
238238

239239
See Percona's "`Storing UUID Values in MySQL`_" post, for more details on
240240
the performance of UUIDs as primary keys.
241241

242242
To minimize these problems, two solutions have been devised:
243243

244-
1. Timestamp first COMBs
245-
2. Ordered-time UUIDs
246-
247-
:ref:`customize.timestamp-first-comb-codec` explains the first solution and how
248-
to use ramsey/uuid to implement it, while :ref:`customize.ordered-time-codec`
249-
explains how to use ramsey/uuid to implement the second solution.
244+
1. :ref:`rfc4122.version6` UUIDs
245+
2. :ref:`rfc4122.version7` UUIDs
250246

251-
.. hint::
247+
.. note::
252248

253-
:ref:`Version 6, reordered time UUIDs <rfc4122.version6>` are a
254-
new version of UUID that take the place of ordered-time UUIDs.
249+
We previously recommended the use of the :ref:`timestamp-first COMB
250+
<customize.timestamp-first-comb-codec>` or :ref:`ordered-time
251+
<customize.ordered-time-codec>` codecs to solve these problems. However,
252+
UUID versions 6 and 7 were defined to provide these solutions in a
253+
standardized way.
255254

256255

257256
.. _ramsey/uuid-doctrine: https://github.com/ramsey/uuid-doctrine

docs/introduction.rst

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ Introduction
55
============
66

77
ramsey/uuid is a PHP library for generating and working with `RFC 4122`_ version
8-
1, 2, 3, 4, and 5 universally unique identifiers (UUID). ramsey/uuid also
9-
supports optional and non-standard features, such as `version 6 UUIDs`_,
10-
GUIDs, and other approaches for encoding/decoding UUIDs.
8+
1, 2, 3, 4, 5, 6, and 7 universally unique identifiers (UUID). ramsey/uuid also
9+
supports optional and non-standard features, such as GUIDs and other approaches
10+
for encoding/decoding UUIDs.
1111

1212
What Is a UUID?
1313
###############
@@ -29,4 +29,3 @@ UUIDs can also be stored in binary format, as a string of 16 bytes.
2929

3030

3131
.. _RFC 4122: https://tools.ietf.org/html/rfc4122
32-
.. _version 6 UUIDs: http://gh.peabody.io/uuidv6/

docs/quickstart.rst

+2
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ library.
9797
- This generates a :ref:`rfc4122.version5` UUID.
9898
* - :php:meth:`Uuid::uuid6() <Ramsey\\Uuid\\Uuid::uuid6>`
9999
- This generates a :ref:`rfc4122.version6` UUID.
100+
* - :php:meth:`Uuid::uuid7() <Ramsey\\Uuid\\Uuid::uuid7>`
101+
- This generates a :ref:`rfc4122.version7` UUID.
100102
* - :php:meth:`Uuid::isValid() <Ramsey\\Uuid\\Uuid::isValid>`
101103
- Checks whether a string is a valid UUID.
102104
* - :php:meth:`Uuid::fromString() <Ramsey\\Uuid\\Uuid::fromString>`

docs/reference.rst

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Reference
1818
reference/rfc4122-uuidv4
1919
reference/rfc4122-uuidv5
2020
reference/rfc4122-uuidv6
21+
reference/rfc4122-uuidv7
2122
reference/guid-fields
2223
reference/guid-guid
2324
reference/nonstandard-fields

docs/reference/rfc4122-uuidv7.rst

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.. _reference.rfc4122.uuidv7:
2+
3+
===============
4+
Rfc4122\\UuidV7
5+
===============
6+
7+
.. php:namespace:: Ramsey\Uuid\Rfc4122
8+
9+
.. php:class:: UuidV7
10+
11+
Implements :php:interface:`Ramsey\\Uuid\\Rfc4122\\UuidInterface`.
12+
13+
UuidV7 represents a :ref:`version 7, Unix Epoch time UUID <rfc4122.version7>`.
14+
In addition to providing the methods defined on the interface, this class
15+
additionally provides the following methods.
16+
17+
.. php:method:: getDateTime()
18+
19+
:returns: A date object representing the timestamp associated with the UUID.
20+
:returntype: ``\DateTimeInterface``

docs/rfc4122.rst

+12-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ RFC 4122 UUIDs
1414
rfc4122/version4
1515
rfc4122/version5
1616
rfc4122/version6
17+
rfc4122/version7
1718

18-
`RFC 4122`_ defines five versions of UUID. Each version has different generation
19-
algorithms and properties. Which one you choose to use depends on your use-case.
20-
You can find out more about their applications on the specific page for that
21-
version.
19+
`RFC 4122`_ defines five versions of UUID, while a `new Internet-Draft under
20+
review`_ defines three new versions. Each version has different generation
21+
algorithms and properties. Which one you choose depends on your use-case. You
22+
can find out more about their applications on the specific page for that version.
2223

2324
Version 1: Gregorian Time
2425
This version of UUID combines a timestamp, node value (in the form of a MAC
@@ -50,5 +51,12 @@ Version 6: Reordered Time
5051
:ref:`version 1 UUID <rfc4122.version1>` with a *monotonically increasing*
5152
UUID. For more details, see :ref:`rfc4122.version6`.
5253

54+
Version 7: Unix Epoch Time
55+
This version of UUID combines a timestamp--based on milliseconds elapsed
56+
since the Unix Epoch--and random bytes to create a monotonically increasing,
57+
sortable UUID without the privacy and entropy concerns associated with
58+
version 1 and version 6 UUIDs. For more details, see :ref:`rfc4122.version7`.
59+
5360

5461
.. _RFC 4122: https://tools.ietf.org/html/rfc4122
62+
.. _new Internet-Draft under review: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04

docs/rfc4122/version1.rst

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
Version 1: Gregorian Time
55
=========================
66

7+
.. attention::
8+
9+
If you need a time-based UUID, and you don't need the other features
10+
included in version 1 UUIDs, we recommend using
11+
:ref:`version 7 UUIDs <rfc4122.version7>`.
12+
713
A version 1 UUID uses the current time, along with the MAC address (or *node*)
814
for a network interface on the local machine. This serves two purposes:
915

docs/rfc4122/version6.rst

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ Version 6: Reordered Time
1111
through the IETF process, the version 6 format is not expected to change
1212
in any way that breaks compatibility.
1313

14+
.. attention::
15+
16+
If you need a time-based UUID, and you don't need the other features
17+
included in version 6 UUIDs, we recommend using
18+
:ref:`version 7 UUIDs <rfc4122.version7>`.
19+
1420
Version 6 UUIDs solve `two problems that have long existed`_ with the use of
1521
:ref:`version 1 <rfc4122.version1>` UUIDs:
1622

@@ -199,7 +205,7 @@ machines, see :ref:`rfc4122.version6.nodes`.
199205

200206
If you do not need an identifier with a node value embedded in it, but you still
201207
need the benefit of a monotonically increasing unique identifier, see
202-
:ref:`customize.timestamp-first-comb-codec`.
208+
:ref:`rfc4122.version7`.
203209

204210

205211
.. _Internet-Draft under review: https://datatracker.ietf.org/doc/draft-peabody-dispatch-new-uuid-format/

docs/rfc4122/version7.rst

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
.. _rfc4122.version7:
2+
3+
==========================
4+
Version 7: Unix Epoch Time
5+
==========================
6+
7+
.. note::
8+
9+
Version 7, Unix Epoch time UUIDs are a new format of UUID, proposed in an
10+
`Internet-Draft under review`_ at the IETF. While the draft is still going
11+
through the IETF process, the version 7 format is not expected to change
12+
in any way that breaks compatibility.
13+
14+
.. admonition:: ULIDs and Version 7 UUIDs
15+
:class: hint
16+
17+
Version 7 UUIDs are binary-compatible with `ULIDs`_ (universally unique
18+
lexicographically-sortable identifiers).
19+
20+
Both use a 48-bit timestamp in milliseconds since the Unix Epoch, filling
21+
the rest with random data. Version 7 UUIDs then add the version and variant
22+
bits required by the UUID specification, which reduces the randomness from
23+
80 bits to 74. Otherwise, they are identical.
24+
25+
You may even convert a version 7 UUID to a ULID.
26+
:ref:`See below for an example. <rfc4122.version7.ulid>`
27+
28+
Version 7 UUIDs solve `two problems that have long existed`_ with the use of
29+
:ref:`version 1 <rfc4122.version1>` UUIDs:
30+
31+
1. Scattered database records
32+
2. Inability to sort by an identifier in a meaningful way (i.e., insert order)
33+
34+
To overcome these issues, we need the ability to generate UUIDs that are
35+
*monotonically increasing*.
36+
37+
:ref:`Version 6 UUIDs <rfc4122.version6>` provide an excellent solution for
38+
those who need monotonically increasing, sortable UUIDs with the features of
39+
version 1 UUIDs (MAC address and clock sequence), but if those features aren't
40+
necessary for your application, using a version 6 UUID might be overkill.
41+
42+
Version 7 UUIDs combine random data (like version 4 UUIDs) with a timestamp (in
43+
milliseconds since the Unix Epoch, i.e., 1970-01-01 00:00:00 UTC) to create a
44+
monotonically increasing, sortable UUID that doesn't have any privacy concerns,
45+
since it doesn't include a MAC address.
46+
47+
For this reason, implementations should use version 7 UUIDs over versions 1 and
48+
6, if possible.
49+
50+
.. code-block:: php
51+
:caption: Generate a version 7, Unix Epoch time UUID
52+
:name: rfc4122.version7.example
53+
54+
use Ramsey\Uuid\Uuid;
55+
56+
$uuid = Uuid::uuid7();
57+
58+
printf(
59+
"UUID: %s\nVersion: %d\nDate: %s\n",
60+
$uuid->toString(),
61+
$uuid->getFields()->getVersion(),
62+
$uuid->getDateTime()->format('r'),
63+
);
64+
65+
This will generate a version 7 UUID and print out its string representation and
66+
the time it was created.
67+
68+
It will look something like this:
69+
70+
.. code-block:: text
71+
72+
UUID: 01833ce0-3486-7bfd-84a1-ad157cf64005
73+
Version: 7
74+
Date: Wed, 14 Sep 2022 16:41:10 +0000
75+
76+
To use an existing date and time to generate a version 7 UUID, you may pass a
77+
``\DateTimeInterface`` instance to the ``uuid7()`` method.
78+
79+
.. code-block:: php
80+
:caption: Generate a version 7 UUID from an existing date and time
81+
:name: rfc4122.version7.example-datetime
82+
83+
use DateTimeImmutable;
84+
use Ramsey\Uuid\Uuid;
85+
86+
$dateTime = new DateTimeImmutable('@281474976710.655');
87+
$uuid = Uuid::uuid7($dateTime);
88+
89+
printf(
90+
"UUID: %s\nVersion: %d\nDate: %s\n",
91+
$uuid->toString(),
92+
$uuid->getFields()->getVersion(),
93+
$uuid->getDateTime()->format('r'),
94+
);
95+
96+
Which will print something like this:
97+
98+
.. code-block:: text
99+
100+
UUID: ffffffff-ffff-7964-a8f6-001336ac20cb
101+
Version: 7
102+
Date: Tue, 02 Aug 10889 05:31:50 +0000
103+
104+
.. tip::
105+
106+
Version 7 UUIDs generated in ramsey/uuid are instances of UuidV7. Check out
107+
the :php:class:`Ramsey\\Uuid\\Rfc4122\\UuidV7` API documentation to learn
108+
more about what you can do with a UuidV7 instance.
109+
110+
111+
.. _rfc4122.version7.ulid:
112+
113+
Convert a Version 7 UUID to a ULID
114+
##################################
115+
116+
As mentioned in the callout above, version 7 UUIDs are binary-compatible with
117+
`ULIDs`_. This means you can encode a version 7 UUID using `Crockford's Base 32
118+
algorithm`_ and it will be a valid ULID, timestamp and all.
119+
120+
Using the third-party library `tuupola/base32`_, here's how we can encode a
121+
version 7 UUID as a ULID. Note that there's a little bit of work to perform the
122+
conversion, since you're working with different bases.
123+
124+
.. code-block:: php
125+
:caption: Encode a version 7, Unix Epoch time UUID as a ULID
126+
:name: rfc4122.version7.example-ulid
127+
128+
use Ramsey\Uuid\Uuid;
129+
use Tuupola\Base32;
130+
131+
$crockford = new Base32([
132+
'characters' => Base32::CROCKFORD,
133+
'padding' => false,
134+
'crockford' => true,
135+
]);
136+
137+
$uuid = Uuid::uuid7();
138+
139+
// First, we must pad the 16-byte string to 20 bytes
140+
// for proper conversion without data loss.
141+
$bytes = str_pad($uuid->getBytes(), 20, "\x00", STR_PAD_LEFT);
142+
143+
// Use Crockford's Base 32 encoding algorithm.
144+
$encoded = $crockford->encode($bytes);
145+
146+
// That 20-byte string was encoded to 32 characters to avoid loss
147+
// of data. We must strip off the first 6 characters--which are
148+
// all zeros--to get a valid 26-character ULID string.
149+
$ulid = substr($encoded, 6);
150+
151+
printf("ULID: %s\n", $ulid);
152+
153+
This will print something like this:
154+
155+
.. code-block:: text
156+
157+
ULID: 01GCZ05N3JFRKBRWKNGCQZGP44
158+
159+
.. caution::
160+
161+
Be aware that all version 7 UUIDs may be converted to ULIDs but not all
162+
ULIDs may be converted to UUIDs.
163+
164+
For that matter, all UUIDs of any version may be encoded as ULIDs, but they
165+
will not be monotonically increasing and sortable unless they are version 7
166+
UUIDs. You will also not be able to extract a meaningful timestamp from the
167+
ULID, unless it was converted from a version 7 UUID.
168+
169+
.. _ULIDs: https://github.com/ulid/spec
170+
.. _Internet-Draft under review: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.2
171+
.. _two problems that have long existed: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/
172+
.. _Crockford's Base 32 algorithm: https://www.crockford.com/base32.html
173+
.. _tuupola/base32: https://packagist.org/packages/tuupola/base32

src/Uuid.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -683,16 +683,20 @@ public static function uuid6(
683683
/**
684684
* Returns a version 7 (Unix Epoch time) UUID
685685
*
686+
* @param DateTimeInterface|null $dateTime An optional date/time from which
687+
* to create the version 7 UUID. If not provided, the UUID is generated
688+
* using the current date/time.
689+
*
686690
* @return UuidInterface A UuidInterface instance that represents a
687691
* version 7 UUID
688692
*/
689-
public static function uuid7(): UuidInterface
693+
public static function uuid7(?DateTimeInterface $dateTime = null): UuidInterface
690694
{
691695
$factory = self::getFactory();
692696

693697
if (method_exists($factory, 'uuid7')) {
694698
/** @var UuidInterface */
695-
return $factory->uuid7();
699+
return $factory->uuid7($dateTime);
696700
}
697701

698702
throw new UnsupportedOperationException(

0 commit comments

Comments
 (0)