A SOLID and lightweight implementation of the UUID spec for PHP +8.2
$ composer require uma/uuid
$v1 = new \UMA\Uuid\Version1Generator('01:23:45:67:89:ab');
(string) $v1->generate();
// c1f45316-dd26-11e7-b25b-0123456789ab
(string) $v1->generate();
// c1f45af4-dd26-11e7-b25b-0123456789ab
(string) $v1->generate();
// c1f45bf8-dd26-11e7-b25b-0123456789ab
A fresh, random 14-bit value is generated every time that the
generate()
method is called on a Version1Generator instance.
This behaviour is compliant with the RFC spec (see section 4.1.5).
$v4 = new \UMA\Uuid\Version4Generator();
(string) $v4->generate();
// 89f18abf-4c99-488c-a7e5-4f3a58e39d44
(string) $v4->generate();
// ff02c008-3318-4464-8f7c-0fc9263830b4
(string) $v4->generate();
// d4d33558-4a48-469e-8456-a46c4c895aac
$ns = \UMA\Uuid\Uuid::fromString(\UMA\Uuid\Version5Generator::NS_DNS);
$v5 = new \UMA\Uuid\Version5Generator($ns);
(string) $v5->generate('foo');
// b84ed8ed-a7b1-502f-83f6-90132e68adef
(string) $v5->generate('bar');
// e8d5cf6d-de0f-5e77-9aa3-91093cdfbf62
This generator is not part of the RFC 4122 spec. It is a variant of the version 4 UUID that was first described (AFAIK) by Jimmy Nilsson in 2002 on an article named The Cost of GUIDs as Primary Keys.
COMB UUIDs are just version 4 UUIDs whose 48 higher bits have been overwritten with a timestamp. This guarantees to a certain extent that the generated values are monotonic (ever increasing), which is a most desired property when such values are used as database primary keys (or simply indexed).
$comb = new \UMA\Uuid\CombGenerator();
(string) $comb->generate();
// 55fef06f-9382-4732-96b4-c13f92f7f254
(string) $comb->generate();
// 55fef06f-9392-4f41-a4c7-e036c1befcd7
(string) $comb->generate();
// 55fef06f-9396-4904-a606-c9b62c8e7aea
The granularity reflects the precision of the timestamp that will be
stamped onto the UUIDs, and is in fact the number of fractional digits
that will be used from a call to microtime(true)
.
var_dump(microtime(true));
// 1513212567.378735
This number can go from 0 (which means second-precision) to 6 (which means microsecond-precision, the maximum that PHP is capable of and also the default value).
Since multiple COMB UUIDs generated with the same timestamp are not guaranteed to
be monotonic, the higher the granularity the stronger is that guarantee and the better
is the output of the CombGenerator
. To bring in some perspective, my laptop
can generate over 100k COMB UUIDs in the same second, but only 1 or 2 in the same microsecond.
The generator will overflow and start again from 10000000-0000
. Of course, that means
that at this point the monotonic property of the generator will be lost!
It depends on the granularity that you have chosen, but the getOverflowDate()
method returns
the exact moment it will occur.
As I write this, the nearest overflow will occur on 2059-03-13 02:56:07 UTC (for a granularity of 5).
$g6 = new \UMA\Uuid\CombGenerator(6);
$g6->getOverflowDate();
// object(DateTimeImmutable)#4 (3) {
// ["date"]=>
// string(26) "2112-09-17 23:53:47.000000"
// ["timezone_type"]=>
// int(1)
// ["timezone"]=>
// string(6) "+00:00"
// }
$g0 = new \UMA\Uuid\CombGenerator(0);
$g0->getOverflowDate();
// object(DateTimeImmutable)#6 (3) {
// ["date"]=>
// string(29) "8921556-12-07 10:44:15.000000"
// ["timezone_type"]=>
// int(1)
// ["timezone"]=>
// string(6) "+00:00"
// }
The SequentialGenerator
does not generate unique UUIDs at all. It can be used
as a replacement for real generators in testing contexts, where readable and
deterministic values might be desirable.
The first parameter of the generator will print a "pattern" on the higher 8 bytes of its UUIDs. The second is the offset at which the counting will begin. Both are optional.
$vanilla = new \UMA\Uuid\SequentialGenerator();
(string) $vanilla->generate();
// 00000000-0000-0000-0000-000000000000
(string) $vanilla->generate();
// 00000000-0000-0000-0000-000000000001
(string) $vanilla->generate();
// 00000000-0000-0000-0000-000000000002
$custom = new \UMA\Uuid\SequentialGenerator(dechex('abcd'), 255);
(string) $custom->generate();
// 00000000-0000-abcd-0000-0000000000ff
(string) $custom->generate();
// 00000000-0000-abcd-0000-000000000100
(string) $custom->generate();
// 00000000-0000-abcd-0000-000000000101
The Uuid
class is modeled as a value object that wraps valid UUID strings. It also has
named constructors to generate Uuid objects when their intended value is known beforehand.
This is the preferred way to create new instances when you have an UUID value in a string variable. The method is case insensitive.
// Creates a new Uuid object from a hardcoded string
$uuid = \UMA\Uuid::fromString('96aaab69-7b76-4461-b008-cbb9cfcb6fdf');
// Passing an invalid value will result in an \InvalidArgumentException thrown
$badUuid = \UMA\Uuid::fromString('abcd');
// If you don't know beforehand if the value is a valid UUID but want
// to avoid the exception you can rely on the `Uuid::isUuid(string): bool` helper.
if (false === \UMA\Uuid::isUuid($unsafeUuid)) {
return;
}
$safeUuid = \UMA\Uuid::fromString($unsafeUuid);
Creates a new Uuid instance from 16 raw bytes. This factory is extensively used by the generators but it can also cover some use cases of the end user of this library, such as when retrieving UUID strings in packed form from a database.
$uuid = \UMA\Uuid::fromBytes(random_bytes(16));
Convenience helper for generating instances of the NIL UUID.
Uuid
objects have two getters: asString()
and asBytes()
. The former returns the UUID
in its canonical textual format, the latter as a raw sequence of 16 bytes.
They can also be casted to string. This is equivalent to calling the asString()
method.
$uuid = (new \UMA\Uuid\CombGenerator)->generate();
$uuid->asBytes();
// ??????M???D????d
$uuid->asString();
// 5608bfbf-4602-4abb-9aa1-7584ab428631
(string) $uuid === $uuid->asString();
// true
UUIDs are often used as surrogate keys for tables in relational databases. This implies that they are indexed, and index performance depends in part on how continuous these values are. Hence, the 'best' kind of UUID is the COMB UUID.
I ran a benchmark on Postgres 9.6 in which I measured the time it took to insert a hundred million pregenerated UUIDs of different kinds. Version 4 UUIDs were inserted about 32 times slower into the table than a conventional autoincrementing integer. In contrast COMB UUIDs were "just" about 40% slower. These results are consistent with the ones reported 15 years ago by Mr. Nilsson in his own article.
In summary, you should use COMB UUIDs unless you have specific reasons to use another kind of UUID.
Since it is exactly the same as Version 5 but relies on an even weaker hashing algorithm I do not see the point of adding it.
As far as I know Version 2 UUIDs were never defined. The spec does not even mention them. Go figure.
Because a few sections of the Version1Generator
and CombGenerator
code rely on integers
being 64 bit wide (in particular, the result of casting the output of microtime(true)
to an integer
may fill up to 56 bits at the time of writing).
If your PHP binary was built for a 32 bit architecture but don't intend to use neither of these time-based generators
you can still use this library, but will need to install it passing the --ignore-platform-reqs
flag to composer.
Yes, just implement the UuidGenerator
interface. Here's a sample
implementation that always returns the same Uuid. This one could be useful
in a unit testing context.
use UMA\Uuid\Uuid;
use UMA\Uuid\UuidGenerator;
class DeterministicGenerator implements UuidGenerator
{
/**
* @var Uuid
*/
private $uuid;
public function __construct(Uuid $uuid)
{
$this->uuid = $uuid;
}
public function generate(string $name = null): Uuid
{
return clone $this->uuid;
}
}
$ composer test
$ composer bench
As it is always the case in PHP, if the xdebug
extension is enabled it will
have a huge impact on runtime performance that will skew the results.